SBCLの出力をパイプして使う場合の注意点
SBCLの出力をパイプして使う場合の注意点。
何も気にせずに書いていると大量のエラー情報が出力されて戸惑うことがある。
例として以下のSBCLスクリプトを用いる。
;;;; ファイル名: loop.lisp ;;;; sbcl-1.0.48 (loop FOR n FROM 0 DO (format t "loop: ~a~%" n))
実行結果。
上のスクリプトだと、Ctrl-Cを使って停止した場合も、大量のエラー出力がでる。
$ sbcl --script loop.lisp loop: 0 loop: 1 loop: 2 loop: 3 loop: 4 loop: 5 loop: 6 loop: 7 Ctrl-C ;; 終了! unhandled SB-SYS:INTERACTIVE-INTERRUPT in thread #<SB-THREAD:THREAD "initial thread" RUNNING {1002961281}>: Interactive interrupt at #x7FFFF79CA8BE. 0: (SB-DEBUG::MAP-BACKTRACE #<CLOSURE (LAMBDA #) {10029F8839}> :START 0 :COUNT 128) 1: (BACKTRACE 128 #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDERR* {1000168001}>) 2: (SB-DEBUG::DEBUGGER-DISABLED-HOOK #<SB-SYS:INTERACTIVE-INTERRUPT {10029F66A1}> #<unavailable argument>) 3: (SB-DEBUG::RUN-HOOK *INVOKE-DEBUGGER-HOOK* #<SB-SYS:INTERACTIVE-INTERRUPT {10029F66A1}>) 4: (INVOKE-DEBUGGER #<SB-SYS:INTERACTIVE-INTERRUPT {10029F66A1}>) 5: (SB-INT:%BREAK SB-UNIX:SIGINT #<SB-SYS:INTERACTIVE-INTERRUPT {10029F66A1}>) 6: ((FLET SB-UNIX::INTERRUPT-IT)) ;; 省略 31: ((FLET SB-FASL::LOAD-STREAM) #<SB-SYS:FD-STREAM for "file /tmp/loop.lisp" {10029699D1}> NIL) 32: (LOAD #<SB-SYS:FD-STREAM for "file /tmp/loop.lisp" {10029699D1}> :VERBOSE NIL :PRINT NIL :IF-DOES-NOT-EXIST T :EXTERNAL-FORMAT :DEFAULT) 33: (SB-IMPL::PROCESS-SCRIPT "/tmp/loop.lisp") 34: (SB-IMPL::TOPLEVEL-INIT) 35: ((LABELS SB-IMPL::RESTART-LISP)) unhandled condition in --disable-debugger mode, quitting
これは、スクリプトを次のように修正すれば解決する。
;; デバッガフックを設定 (setf sb-ext:*invoke-debugger-hook* (lambda (condition hook) (declare (ignore conditoin hook)) ;; デバッガが呼ばれたら、単にプログラムを終了する (sb-ext:quit))) (loop FOR n FROM 0 DO (format t "loop: ~a~%" n)) #| ;; 以下の方法でも良い (handler-case (loop FOR n FROM 0 DO (format t "loop: ~a~%" n)) (condition () (sb-ext:quit))) |#
実行結果。
エラーは出力されない。
$ sbcl --script loop.lisp loop: 0 loop: 1 loop: 2 loop: 3 loop: 4 loop: 5 loop: 6 loop: 7 Ctrl-C ;; 終了!
ただ、これでもまだ問題がある。
エラーが出力される典型的なケースは、出力をパイプでheadコマンドに繋いだ場合。
$ sbcl --script loop.lisp | head -5 loop: 0 loop: 1 loop: 2 loop: 3 loop: 4 Help! 11 nested errors. SB-KERNEL:*MAXIMUM-ERROR-DEPTH* exceeded. 0: (SB-DEBUG::MAP-BACKTRACE #<CLOSURE (LAMBDA (SB-DEBUG::FRAME)) {1002A1BC39}> :START 0 :COUNT 1152921504606846975) 1: (BACKTRACE 1152921504606846975 #<SYNONYM-STREAM :SYMBOL SB-SYS:*TTY* {10001A6831}>) 2: ((LAMBDA NIL)) 3: (SB-IMPL::%WITH-STANDARD-IO-SYNTAX #<CLOSURE (LAMBDA NIL) {1002A1BBF9}>) 4: (SB-IMPL::ERROR-ERROR "Help! " 11 " nested errors. " "SB-KERNEL:*MAXIMUM-ERROR-DEPTH* exceeded.") 5: (SB-IMPL::INFINITE-ERROR-PROTECTOR) 6: (ERROR SB-INT:SIMPLE-STREAM-ERROR :STREAM #<SB-SYS:FD-STREAM for "standard output" {1002961B11}> :FORMAT-CONTROL "~@<~?: ~2I~_~A~:>" :FORMAT-ARGUMENTS ("Couldn't write to ~s" (#<SB-SYS:FD-STREAM for "standard output" {1002961B11}>) "Broken pipe")) 7: (SB-IMPL::SIMPLE-STREAM-PERROR "Couldn't write to ~s" #<SB-SYS:FD-STREAM for "standard output" {1002961B11}> 32) ;; 略 82: (SB-IMPL::SIMPLE-STREAM-PERROR "Couldn't write to ~s" #<SB-SYS:FD-STREAM for "standard output" {1002961B11}> 32) 83: (SB-IMPL::FLUSH-OUTPUT-BUFFER #<SB-SYS:FD-STREAM for "standard output" {1002961B11}>) 84: (FORCE-OUTPUT #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDOUT* {10001E4BB1}>) 85: (SB-INT:FLUSH-STANDARD-OUTPUT-STREAMS) 86: ((LABELS SB-IMPL::RESTART-LISP)) debugger invoked on a SIMPLE-ERROR in thread #<THREAD "initial thread" RUNNING {1002961281}>: Maximum error nesting depth exceeded Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL. (no restarts: If you didn't do this on purpose, please report it as a bug.) ("no debug information for frame") 0] abort ;; デバッグモードに入ってしまうので abort を入力して抜ける
詳しく調べてはいないので原因は不明だが、headコマンドがsbclからの入力を閉じた後も、そのストリームに(sbclが)出力を試みているために、エラーが生じているように見える。
スクリプトをさらに次のように修正すると、この問題も解決する。
;; デバッガフックを設定 (setf sb-ext:*invoke-debugger-hook* (lambda (condition hook) (declare (ignore conditoin hook)) ;; デバッガが呼ばれたら、単にプログラムを終了する ;; recklessly-p に t を指定して、後始末(標準出力ストリームのフラッシュ等)が行われないようにする (sb-ext:quit :recklessly-p t))) (loop FOR n FROM 0 DO (format t "loop: ~a~%" n))
実行結果。
headコマンドにパイプした場合もエラーは出力されない。
$ sbcl --script loop.lisp | head -n loop: 0 loop: 1 loop: 2 loop: 3 loop: 4
sb-ext:quit関数のrecklessly-pをnilにすると、スクリプト終了時に後始末のための処理が走って、その中で(既にheadコマンドによって閉じられている)標準出力ストリームへの出力処理が入っていたことが原因の模様(かなり推測)。