常见的Lisp:启动具有不同工作目录的子进程,而不是Lisp进程

4
假设我有一个名为A的目录和一个子目录B。我进入A并启动lisp。在这个lisp进程中,我想要启动一个Python子进程,其中Python将B视为其当前工作目录。lisp进程需要在A中拥有cwd,而python进程应该在B中拥有cwd。如何以跨平台、简单的方式实现这一点?
我正在寻找适用于CCL和SBCL(可能使用'run-program'函数)的解决方案,并适用于Windows、Linux和OS X。
我查看了CCL run-program文档,但没有看到更改启动进程的cwd的方法。
我查看了Python命令行参数,但没有看到可以更改python进程的cwd的参数。
我考虑运行'cd B; python...'的run-program调用,但我不确定它会产生什么效果,因为它实际上运行了两个程序;cd,然后是python。
Python代码被提供为输入(作为文件),因此我不能更改任何代码(例如添加os.chdir()调用或类似内容)。
启动Python输入文件作为子进程的Python包装器文件并不理想,因为我正在发送stdin并监听由lisp启动的python进程的stdout。在lisp和python进程之间添加另一个子进程来评估输入文件意味着我需要做大量的stdout/stdin中继,并且我有一种感觉这将是脆弱的。
krzysz00的方法非常有效。由于目录更改在启动Python进程之前在lisp中处理,因此这种方法适用于在不同子目录中启动其他进程(不仅限于python)。
以下是使用krzsz00的方法适用于SBCL和CCL的代码。请注意,它使用Hoyte的defmacro! 宏,来自Let Over Lambda,以轻松避免不需要的变量捕获:
#+:SBCL
(defun cwd (dir)
  (sb-posix:chdir dir))

(defun getcwd ()
  #+SBCL (sb-unix:posix-getcwd)
  #+CCL (current-directory))

(defmacro! with-cwd (dir &body body)
  `(let ((,g!cwd (getcwd)))
     (unwind-protect (progn
                       (cwd ,dir)
                       ,@body)
     (cwd ,g!cwd))))

使用方法:

(with-cwd "./B"
  (run-program ...))
3个回答

6
为了以跨平台的方式运行外部程序(例如你的 Python 进程),请参考 external-program。若要更改当前工作目录,请使用以下稍加修改(公共领域)函数 cwd(源代码在 http://files.b9.com/lboot/utils.lisp 中),如下所示。
(defun cwd (&optional dir)
  "Change directory and set default pathname"
  (cond
   ((not (null dir))
    (when (and (typep dir 'logical-pathname)
           (translate-logical-pathname dir))
      (setq dir (translate-logical-pathname dir)))
    (when (stringp dir)
      (setq dir (parse-namestring dir)))
    #+allegro (excl:chdir dir)
    #+clisp (#+lisp=cl ext:cd #-lisp=cl lisp:cd dir)
    #+(or cmu scl) (setf (ext:default-directory) dir)
    #+cormanlisp (ccl:set-current-directory dir)
    #+(and mcl (not openmcl)) (ccl:set-mac-default-directory dir)
    #+openmcl (ccl:cwd dir)
    #+gcl (si:chdir dir)
    #+lispworks (hcl:change-directory dir)
    #+sbcl (sb-posix:chdir dir)
    (setq cl:*default-pathname-defaults* dir))
   (t
    (let ((dir
       #+allegro (excl:current-directory)
       #+clisp (#+lisp=cl ext:default-directory #-lisp=cl lisp:default-directory)
       #+(or cmu scl) (ext:default-directory)
       #+sbcl (sb-unix:posix-getcwd/)
       #+cormanlisp (ccl:get-current-directory)
       #+lispworks (hcl:get-working-directory)
       #+mcl (ccl:mac-default-directory)
       #-(or allegro clisp cmu scl cormanlisp mcl sbcl lispworks) (truename ".")))
      (when (stringp dir)
    (setq dir (parse-namestring dir)))
      dir))))

将这两个函数结合起来,你需要的代码如下:
(cwd #p"../b/")
(external-program:start "python" '("file.py") :output *pythins-stdout-stream* :input *pythons-stdin-stream*)
(cwd #p"../a/")

这将会cd到B,以类似于python file.py &的方式运行Python进程,将Python进程的标准输入/输出发送到指定的流(查看external-program文档获取更多详细信息),最后执行另一个cwd,使Lisp进程返回A。如果Lisp进程应该等待Python进程完成,则使用external-program:run而不是external-program:start

好的建议,谢谢;我没有使用外部程序兼容层,因为我只需要它用于sbcl和ccl,这些的run-program基本相同,而且我不能轻松地为这段代码加载包,因为它不是在服务器端使用的。 - Clayton Stanley
这里是否涉及到多线程程序的影响,或者说cwd是在每个线程上更改的? - Inaimathi
我认为根据我快速浏览的thread_safety(5)cwd是每个线程的,但我并不完全确定。看看它是否有效。 - krzysz00

1

我最终将krzysz00的建议写成了一个软件包,可以在这里找到。

然后有人指出UIOP带有getcwdchdir。如果你使用的是相对较新的Lisp版本,则UIOP应该已经包含在asdf中。


0

我添加了一个额外的限制条件。在Lisp和最终的Python进程之间插入一个Python包装器进程并不理想;但也许我可以启动一个Python进程,加载一个Python包装器文件;该文件将cd,然后加载Python输入文件? - Clayton Stanley
我必须说,这是一个非常棘手的问题。http://cybertiggyr.com/gene/pathnames-0/node11.html - apple16
@Epic_orange:谢天谢地有人写了可移植层。 - krzysz00
问题是关于在Common Lisp中实现它的。 - ealfonso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接