2012-01-11 53 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