带有SBCL的二元管

时间:2019-09-23 14:58:05

标签: subprocess common-lisp sbcl

如何使用sb-ext:run-program在子进程的stdin和stdout上设置二进制管道?我想让元素类型为(unsigned-byte 8)的流与Lisp中的子进程进行对话。

run-program带有一个:external-format参数,但据我所知,它仅涉及文本编码,而不涉及二进制。 SBCL带有test program,它执行二进制I / O,但它使用Gray流定义了一个自定义流类,该类似乎足够先进,必须有一种更简单的方法。

2 个答案:

答案 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