Bash 更改运行脚本的差异

Ser*_*sly 3 bash

bash我对编辑正在运行的脚本时的两种看似不同的行为感到摸不着头脑。

这里不是讨论为什么要这样做的地方(你可能不应该这样做)。我只想尝试了解发生了什么以及为什么。

示例A:

$ echo "echo 'echo hi' >> script.sh" > script.sh
$ cat script.sh
echo 'echo hi' >> script.sh
$ chmod +x script.sh
$ ./script.sh
hi
$ cat script.sh
echo 'echo hi' >> script.sh
echo hi
Run Code Online (Sandbox Code Playgroud)

脚本自行编辑,并直接执行更改(额外的回显行)。多次执行会产生更多行“hi”。

示例B:

创建一个脚本infLoop.sh并运行它。

$ cat infLoop.sh
while true
do
    x=1
    echo $x
done
$ ./infLoop.sh
1
1
1
...
Run Code Online (Sandbox Code Playgroud)

现在打开第二个 shell 并编辑文件,更改x. 例如这样:

$ sed --in-place 's/x=1/x=2/' infLoop.sh
$ cat infLoop.sh
while true
do
    x=2
    echo $x
done
Run Code Online (Sandbox Code Playgroud)

然而,我们观察到第一个终端的输出仍然是1。仅使用一个终端执行相同操作,中断infLoop.shthrough Ctrl+Z,编辑,然后继续 viafg会产生相同的结果。

问题

为什么示例 A 中的更改会立即生效,而示例 B 中的更改却不会立即生效?

PS:我知道有一些问题显示了类似的例子,但我看到的这些问题都没有答案来解释场景之间的差异。

Gor*_*son 5

实际上有两个不同的原因导致示例 B 不同,其中任何一个都足以阻止更改生效。它们是由于文件如何交互以及如何与文件交互(以及类 UNIX 操作系统如何处理文件)的一些微妙之处造成的sedbash并且可能会因稍有不同的程序而有所不同,等等。

总的来说,我想说这是一个很好的例子,说明如果您在运行文件(或从中读取等)的同时修改文件,理解和预测会发生什么是多么困难,因此为什么这是一个坏主意像这样的事情。基本上,这在计算机中相当于锯掉你所站的树枝

原因 1:尽管有该选项的名称,但sed --in-place实际上并未就地修改现有文件。它实际上所做的是创建一个具有临时名称的新文件,然后在完成后删除原始文件并将新文件重命名到其位置。它具有相同的名称,但实际上不是同一个文件。您可以通过查看文件的索引节点号来判断这一点ls -li

$ ls -li infLoop.sh 
88 -rwxr-xr-x 1 pi pi 39 Aug  4 22:04 infLoop.sh
$ sed --in-place 's/x=1/x=2/' infLoop.sh
$ ls -li infLoop.sh 
4073 -rwxr-xr-x 1 pi pi 39 Aug  4 22:05 infLoop.sh
Run Code Online (Sandbox Code Playgroud)

但 bash 仍然打开旧文件(严格来说,它有一个指向旧文件的打开文件句柄),因此无论新文件发生什么变化,它都会继续获取旧内容。

请注意,这并不适用于所有编辑文件的程序。vim例如,将重写现有文件的内容(除非文件权限禁止,在这种情况下它将切换到删除和替换方法)。附加>>总是附加到现有文件而不是创建新文件。

(顺便说一句,如果 bash 可以在“删除”后打开一个文件,这看起来很奇怪,那只是类 UNIX 操作系统处理文件的方式的一部分。直到最后一个目录条目被删除并且最后一个打开的文件才被真正删除。引用它们的文件句柄被关闭。有些程序实际上利用这一点来确保安全,打开(/创建)一个文件,然后立即“删除”它,这样打开的文件句柄是访问该文件的唯一方法。)

原因 2:即使您使用的工具实际修改了现有文件,bash 仍然看不到更改。这样做的原因是 bash 从文件中读取(解析)直到它有可以执行的东西,运行它,然后返回并读取更多内容,直到它有另一个可执行块,等等。它不会返回并重新-读取相同的块以查看它是否已更改,因此它只会注意到尚未读取的文件部分的更改。

在示例 B 中,它必须先读取并解析整个while true ... done循环,然后才能开始执行它。因此,一旦循环被读取并开始执行,该部分(或可能之前)的更改将不会被注意到。循环退出后(如果有的话),循环后文件中的更改将会被注意到。

有关此问题的更多信息,请参阅我对此问题的回答(以及 Don Hatch 对此的评论)。