5

我正在尝试在 SBCL 中运行一个外部程序并捕获它的输出。输出是二进制数据(png 图像),而 SBCL 坚持将其解释为字符串。

我尝试了很多方法,比如

(trivial-shell:shell-command "/path/to/png-generator" :input "some input")

(with-input-from-string (input "some input")
  (with-output-to-string (output)
    (run-program "/path/to/png-generator" () :input input :output output))


(with-input-from-string (input "some input")
  (flexi-streams:with-output-to-sequence (output)
    (run-program "/path/to/png-generator" () :input input :output output))

但我得到了像这样的错误

Illegal :UTF-8 character starting at byte position 0.

在我看来,SBCL 正试图将二进制数据解释为文本并对其进行解码。我该如何改变这种行为?我只对获得八位字节向量感兴趣。

编辑:由于从上面的文本中不清楚,我想补充一点,至少在 flexi-stream 的情况下,流的元素类型是 a flexi-streams:octect(它是 a (unsigned-byte 8))。我希望至少在这种情况下run-program读取原始字节没有很多问题。相反,我收到一条消息Don't know how to copy to stream of element-type (UNSIGNED-BYTE 8)

4

3 回答 3

5

编辑:我很生气无法完成这个非常简单的任务并解决了问题。

从功能上讲,将 UNSIGNED-BYTE 类型的流发送到运行程序并使其正常工作的能力受到严重限制,原因我不明白。我尝试了灰色流、flexi-streams、fd 流和其他一些机制,就像你一样。

但是,仔细阅读 run-program 的源代码(第五次或第六次),我注意到有一个选项 :STREAM 可以传递给输出。鉴于此,我想知道 read-byte 是否会起作用……确实如此。对于更高性能的工作,可以确定如何获取非文件流的长度并在其上运行 READ-SEQUENCE。

(let* 
       ;; Get random bytes
      ((proc-var (sb-ext:run-program "head" '("-c" "10" "/dev/urandom")
                                     :search t
       ;; let SBCL figure out the storage type. This is what solved the problem.
                                     :output :stream))
       ;; Obtain the streams from the process object.
       (output (process-output proc-var))
       (err (process-error proc-var)))
  (values
   ;;return both stdout and stderr, just for polish.
   ;; do a byte read and turn it into a vector.
   (concatenate 'vector
                ;; A byte with value 0 is *not* value nil. Yay for Lisp!
                (loop for byte = (read-byte output nil)
                   while byte
                   collect byte))
   ;; repeat for stderr
   (concatenate 'vector
                (loop for byte = (read-byte err nil)
                   while byte
                   collect byte))))
于 2012-01-11T17:34:03.553 回答
3

如果你愿意使用一些外部库,这可以通过 babel-streams 来完成。这是我用来安全地从程序中获取内容的功能。我使用 :latin-1 因为它将前 256 个字节映射到字符。您可以删除八位字节到字符串并拥有向量。

如果你也想要 stderr,你可以使用嵌套的“with-output-to-sequence”来获得两者。

(defun safe-shell (command &rest args)                                                                                                           
  (octets-to-string                                                                                                                              
   (with-output-to-sequence (stream :external-format :latin-1)                                                                                   
     (let ((proc (sb-ext:run-program command args :search t :wait t :output stream)))                                                            
       (case (sb-ext:process-status proc)                                                                                                        
         (:exited (unless (zerop (sb-ext:process-exit-code proc))                                                                                
                    (error "Error in command")))                                                                                                 
         (t (error "Unable to terminate process")))))                                                                                            
   :encoding :latin-1))                                                                                                                          
于 2012-01-12T06:47:09.240 回答
2

Paul Nathan 已经给出了一个非常完整的答案,关于如何从程序中读取 I/O 为二进制,所以我将添加为什么你的代码不起作用:因为你明确要求SBCL 将 I/O 解释为字符串UTF-8 字符,使用with-{in,out}put-to-string.

另外,我想指出的是,您无需深入run-program了解 's source code 即可获得解决方案。SBCL 的手册中清楚地记录了它。

于 2013-08-09T16:10:17.170 回答