2012-01-11 98 views
5

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

我尝试了许多方法,如

(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的情况下,流的元素类型是flexi-streams:octect(这是一个(unsigned-byte 8))。 我希望至少在这种情况下run-program读取原始字节没有太多问题。相反,我收到一条消息,如Don't know how to copy to stream of element-type (UNSIGNED-BYTE 8)

回答

4

编辑:我生气了,无法做到这一非常简单的任务,并解决了问题。

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

但是,仔细阅读运行程序的源代码(第五次或第六次),我注意到有一个选项:您可以传递给输出的STREAM。鉴于此,我想知道读取字节是否会起作用......并且它确实如此。对于更高性能的工作,可以确定如何获取非文件流的长度并在其上运行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)))) 
+0

是的,这似乎工作,非常感谢你!无论如何,我不知道问题出在哪里。我的意思是,使用文件流作为输出工作正常,所以问题不是完全在运行程序中,而是在字符串流和运行程序之间的交互。但我期望使用with-output-to-sequence可以正常工作。无论如何,至少我现在有一个解决方案。再次感谢。 – 2012-01-13 11:35:57

+0

@MarcoRighele:在SO上,如果你关心接受一个答案,它将问题标记为在SO系统中回答 - 这是选票按钮的复选标记。 – 2012-01-13 16:46:47

+0

如果正在等待查看其他解决方案是否也在工作。无论如何,我更喜欢这个,因为它具有较少的外部依赖性。 – 2012-01-16 08:47:52

2

如果你愿意使用一些外部库,这可以用babel-streams来完成。这是我用来安全地从程序中获取内容的功能。我使用拉丁-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))                               
+0

我在运行示例时遇到问题。使用Linux下的SBCL,我得到了以下警告:ENCODING不是已知的参数关键字,运行中的safe-shell给我“未知字符编码:#”。我错过了什么吗? – 2012-01-13 11:38:41

+0

不完全确定没有知道您正在使用的SBCL和babel版本。您也可以尝试:iso-8859-1,因为这是它的规范名称。确保OCTETS-TO-STRING来自BABLE。 – 2012-01-14 00:33:46

+0

啊,是的,我正在使用sb-ext:octects-to-string。有了正确的功能和sbcl的最新版本,它似乎能够正常工作。非常感谢。 – 2012-01-16 08:40:09

2

保罗弥敦道已经给了一个相当完整的答案,如何从程序二进制读取I/O,所以我就补充为什么你的代码没有工作:因为你明确要求 SBCL使用with-{in,out}put-to-string将I/O解释为一串UTF-8字符。

此外,我想指出,您不需要远达run-program的源代码即可找到解决方案。这清楚地记录在SBCL's manual

+0

对于'with-output-to-string'(它具有'character'的元素类型)当然适用,但不适用于flexi-stream的情况,其中流由octects组成。我预计运行程序会根据流读取正确的'element-type'元素,但事实并非如此。无论如何,我现在意识到这些示例并不十分清晰,我会将更多详细信息放在最后的错误消息 – 2013-08-14 07:40:12

+0

但您会注意到,您没有得到与flexi-stream相同的错误。如果您查看错误消息和堆栈跟踪,您会看到一个公平的猜测是SBCL不使用任何写入功能,而是使用一些特定于实现的优化,并且使用flexi-stream失败。 – 2013-08-15 16:55:34