如何使用sb-ext:run-program
在子进程的stdin和stdout上设置二进制管道?我想让元素类型为(unsigned-byte 8)
的流与Lisp中的子进程进行对话。
run-program
带有一个:external-format
参数,但据我所知,它仅涉及文本编码,而不涉及二进制。 SBCL带有test program,它执行二进制I / O,但它使用Gray流定义了一个自定义流类,该类似乎足够先进,必须有一种更简单的方法。
答案 0 :(得分:4)
通常,sb-ext:run-program
负责在传递:stream
选项时创建中间流。另一个答案表明,您可以根据需要直接向其写入字节。但是,如果您检查run-program
的实现方式,则可以使用run-program
调用的相同函数自己生成流,以生成中间的unix管道,并使用二进制流对其进行读写。>
(defpackage :so (:use :cl :alexandria))
(in-package :so)
定义一个辅助函数,在将错误作为警告处理时关闭文件描述符:
(defun unix-close/warn-on-error (file-descriptor)
(multiple-value-bind (status error) (sb-unix:unix-close file-descriptor)
(prog1 status
(unless (eql error 0)
(warn "Unix close error: ~S" error)))))
然后是一个临时创建unix管道的宏:
(defmacro with-unix-pipe ((read-fd write-fd) &body body)
(with-gensyms (first second)
`(multiple-value-bind (,first ,second) (sb-unix:unix-pipe)
(if ,first
(unwind-protect
(multiple-value-bind (,read-fd ,write-fd)
(values ,first ,second)
,@body)
(unix-close/warn-on-error ,first)
(unix-close/warn-on-error ,second))
(error "Unix pipe error: ~s" ,second)))))
但是,运行程序需要流,而不是文件描述符。这里有一个宏,该宏将变量绑定到与文件描述符绑定的流:
(defmacro with-fd-stream% ((var fd direction &rest fd-args) &body body)
(check-type direction (member :output :input))
(with-gensyms (in%)
`(let ((,in% (sb-sys:make-fd-stream ,fd ,direction t ,@fd-args)))
(unwind-protect (let ((,var ,in%))
(declare (dynamic-extent ,var))
,@body)
(close ,in%)))))
如果输入/输出文件描述符为一对,则对宏执行相同的操作:
(defmacro with-fd-streams (((in read-fd &rest read-args)
(out write-fd &rest write-args))
&body body)
`(with-fd-stream% (,in ,read-fd :input ,@read-args)
(with-fd-stream% (,out ,write-fd :output ,@write-args)
,@body)))
最后,您可以使用以下代码测试代码:
(let ((ub8 '(unsigned-byte 8)))
(with-unix-pipe (read write)
(with-fd-streams ((in read :element-type ub8)
(out write :element-type ub8))
(fresh-line)
(sb-ext:run-program "dd"
'("if=/dev/random" "count=1" "bs=64")
:search t
:output out
:error nil
:wait nil
:status-hook (lambda (p)
(unless (sb-ext:process-alive-p p)
(close out))))
(sb-ext:run-program "hd"
'()
:search t
:input in
:output *standard-output*
:wait t))))
答案 1 :(得分:3)
您链接的第一个测试似乎已经表明,您可以简单地将字节发送到使用:input :stream
和:output :stream
创建的流中。
为可移植性,我建议使用uiop:launch-program
代替
(let ((pri (uiop:launch-program "cat" :input :stream :output :stream)))
(write-byte 43 (uiop:process-info-input pri))
(force-output (uiop:process-info-input pri))
(read-byte (uiop:process-info-output pri)))
=> 43