Shell-Script

在腳本執行時解析腳本是否普遍存在於 shell 中或存在於其他解釋器中,它是如何工作的?

  • June 14, 2018

我一直認為 shell 會解析整個腳本,構造一個 AST,然後從記憶體中執行那個 AST。但是,我剛剛閱讀了 Stéphane Chazelas 的評論,並測試了執行這個腳本,edit-while-executing.sh:

#!/bin/bash

echo start
sleep 10

然後在它睡覺的時候執行:

$ echo "echo end" >> edit-while-executing.sh

它使它在最後列印“結束”。

但是,當嘗試修改它時:

#!/bin/bash

while true; do
 echo yes
done

通過做:

$ printf "%s" "no " | dd of=edit-while-executing.sh conv=notrunc seek=35 bs=1

它沒有用,並繼續列印“是”。

我還想知道其他非shell解釋器是否也這樣工作,並嘗試了與python的第一個腳本等效的方法,但它不起作用。不過,也許 python 不再是解釋器,它更像是一個 JIT 編譯器。

所以重申我的問題,這是一種普遍存在於 shell 並且僅限於它們的行為,還是存在於其他解釋器(那些不被視為 shell 的解釋器)中?另外,這是如何工作的,我可以進行第一次修改但不能進行第二次修改?

所以,這會在 Bash/dash/ksh/zsh 中無限期地執行(或者至少直到你的磁碟填滿):

#!/bin/sh
s=$0
foo() { echo "hello"; echo "foo" >> $s; sleep .1; }
foo

需要注意的是,只有在 shell 讀取的最後一行之後添加到腳本文件中的內容才重要。如果輸入是管道,shell 不會返回重新讀取早期的部分,即使輸入是管道也無法做到這一點。

類似的結構在 Perl 中不起作用,它在執行之前讀取整個文件。

#!/usr/bin/perl -l    
open $fd, ">>", $0;
sub foo { print "hello"; print $fd 'foo;' }
foo;

我們可以看到,當通過管道輸入時它也會這樣做。這會在 1 秒後給出語法錯誤(並且僅此錯誤):

$ (echo 'printf "hello\n";' ; sleep 1 ; echo 'if' ) | perl 

當相同的腳本通過管道傳輸到例如 Bash 時,列印hello,然後在一秒鐘後拋出語法錯誤。

Python 看起來類似於帶有管道輸入的 Perl,即使解釋器在互動時執行 read-eval-print 循環。


除了逐行閱讀輸入腳本之外,至少 Bash 和 dash 一次將參數處理為eval一行:

$ cat evaltest.sh
var='echo hello
fi'
eval "$var"
$ bash evaltest.sh
hello
evaltest.sh: eval: line 4: syntax error near unexpected token `fi'
evaltest.sh: eval: line 4: `fi'

Zsh 和 ksh 立即給出錯誤。

與源腳本類似,這次 Zsh 也像 Bash 和 dash 一樣逐行執行:

$ cat sourceme.sh
echo hello
fi
$ zsh -c '. ./sourceme.sh'
hello
./sourceme.sh:2: parse error near `fi'

此功能存在於其他提供所謂的read eval print loop. LISP 是一種非常古老的語言,具有這樣的功能,Common LISP 有一個read函式可以在這裡讀取表達式(+ 2 2),然後可以傳遞給它eval進行評估(儘管在實際程式碼中,出於各種安全原因,您可能不想這樣做):

% sbcl
* (defparameter sexp (read))
(+ 2 2)

SEXP
* (print (eval sexp))

4
4

我們也可以定義我們自己的非常簡單的 REPL,而不需要太多的特性或調試或其他任何東西,但這確實顯示了 REPL 部分:

* (defun yarepl () (loop (print (eval (read))) (force-output) (fresh-line)))

YAREPL
* (yarepl)
(* 4 2)

8
(print "hi")

"hi"
"hi"

基本上就像銘牌上所說的那樣,數據被讀入、評估、列印,然後(假設沒有崩潰並且仍然有電或為設備供電的東西)它循環回讀取不需要提前建構 AST。(SBCL 出於顯示原因需要force-outputfresh-line添加,其他 Common LISP 實現可能會也可能不會。)

REPL 的其他內容包括 TCL(“被放射性 LISP 咬住的外殼”),其中包括帶有 Tk 的圖形內容

% wish
wish> set msg "hello"
hello
wish> pack [label .msg -textvariable msg]
wish> wm geometry . 500x500
wish> exit

或者在這裡定義一個函式f>c來進行溫度轉換(“ok”被添加gforth):

% gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: f>c ( f -- c ) 32 - 5 9 */ cr . cr ;  ok
-40 f>c
-40
ok
100 f>c
37
ok
bye

引用自:https://unix.stackexchange.com/questions/449593