You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SBCL编译可执行文件中read-line为何读取为符号而非字符串?

问题:Common Lisp编译可执行文件处理管道输入报错

我尝试用Common Lisp过滤bash的locate命令输出,仅保留目录,为此创建了dirfilter.lisp脚本,代码如下:

#!/usr/bin/env -S sbcl --script

(let (line)
  (loop while (setq line (read-line))
    when (not (pathname-name (probe-file line)))
    do (princ line) (terpri)))

使用locate -i hello | ./dirfilter.lisp运行时一切正常。但移除shebang行后,通过SBCL REPL执行以下代码编译为独立可执行文件:

(load (compile-file "dirfilter.lisp"))

(save-lisp-and-die "dirfilter" :executable t)

运行locate -i hello | ./dirfilter时出现错误:

debugger invoked on a UNBOUND-VARIABLE @52BBE314 in thread
#<THREAD tid=203029 "main thread" RUNNING {1001448003}>:
The variable /HOME/DALANICOLAI/HELLO.PNG is unbound.

我判断该错误是因为可执行文件中read-line未将输入读取为字符串而是符号,请问这是什么原因,该如何修复?


原因与修复方法

错误原因

当用save-lisp-and-die生成独立可执行文件时,SBCL默认会进入交互REPL模式,此时它会将标准输入的内容当作Lisp代码解析,而非纯文本字符串。像/HOME/DALANICOLAI/HELLO.PNG这种以斜杠开头的路径,会被Lisp读取器解析为符号,而非字符串,自然会触发"未绑定变量"的错误——因为程序试图引用这个不存在的符号,而不是处理路径字符串。

另外,原代码没有指定可执行文件的入口逻辑,导致SBCL启动后默认进入交互读取模式,完全没执行你写的过滤循环。

修复方案

修改代码,明确指定程序入口函数,让可执行文件启动时直接执行过滤逻辑,而非进入交互REPL。

修正后的代码

(defun main ()
  (let (line)
    (handler-case
        (loop while (setq line (read-line *standard-input* nil nil))
              when (not (pathname-name (probe-file line)))
              do (princ line) (terpri))
      (end-of-file () nil))))

(save-lisp-and-die "dirfilter" :executable t :toplevel #'main)

修改说明

  1. 封装入口函数:把过滤逻辑放到main函数中,清晰定义程序执行入口
  2. 指定顶层函数:通过:toplevel #'main参数告诉SBCL,可执行文件启动时直接调用main函数
  3. 处理文件结束:添加handler-case捕获end-of-file异常,避免输入流结束时抛出错误
  4. 明确输入流:指定*standard-input*作为read-line的输入源,消除歧义

编译与运行

  1. 移除原代码中的shebang行,将上述代码保存为dirfilter.lisp
  2. 在SBCL REPL中加载代码并执行编译:
    (load "dirfilter.lisp")
    
  3. 生成的dirfilter可执行文件即可正常处理管道输入:
    locate -i hello | ./dirfilter
    

兼容脚本与编译模式的版本

如果需要同时支持shebang脚本运行和编译为可执行文件,可以用条件编译:

#!/usr/bin/env -S sbcl --script

(defun main ()
  (let (line)
    (handler-case
        (loop while (setq line (read-line *standard-input* nil nil))
              when (not (pathname-name (probe-file line)))
              do (princ line) (terpri))
      (end-of-file () nil))))

#-script (save-lisp-and-die "dirfilter" :executable t :toplevel #'main)
#+script (main)
  • 用shebang运行时(./dirfilter.lisp),#+script分支触发,直接执行main
  • 编译时,#-script分支触发,生成指定入口的可执行文件

内容的提问来源于stack exchange,提问作者dalanicolai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 23:30:17