Bash保存后会自动将更新重新加载(注入)到正在运行的脚本中:为什么?有实际用途吗?


10

我正在编写bash脚本,并且在脚本等待while循环中的某些输入时碰巧更新了代码(将脚本文件保存到磁盘)。回到终端并继续执行脚本的先前操作后,bash给出了有关文件语法的错误:

/home/aularon/bin/script: line 58: unexpected EOF while looking for matching `"'
/home/aularon/bin/script: line 67: syntax error: unexpected end of file

因此,我尝试执行以下操作:

第一个:创建脚本,self-update.sh我们称它为:

#!/bin/bash
fname=$(mktemp)
cat $0 | sed 's/BEFORE\./AFTER!./' > $fname
cp $fname $0
rm -f $fname
echo 'String: BEFORE.';

脚本所做的是读取其代码,将单词“之前”更改为“之后”,然后使用新代码重新编写自身。

第二次运行:

chmod +x self-update.sh
./self-update.sh

第三奇迹

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.

现在,我不会猜到在相同的调用之后它将输出,当然可以在第二次运行中进行,但不能在第一次运行中进行。

所以我的问题是:(故意)是故意的吗?还是因为bash运行脚本的方式?逐行或逐条命令。有没有很好的利用这种行为?有什么例子吗?


编辑:我试图重新格式化文件以将所有命令放在一行中,它现在不起作用:

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;echo 'String: BEFORE.';

输出:

aularon@aularon-laptop:~$ ./self-update.sh #First invocation
String: BEFORE.
aularon@aularon-laptop:~$ ./self-update.sh #Second invocation
String: AFTER!.

在将echo字符串移至下一行时,将其与rewrite(cp)调用分开:

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;
echo 'String: BEFORE.';

现在它又可以工作了:

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.


1
我已经仔细阅读了源代码,但是对此并没有任何解释。我非常确定,尽管我已经看到一些自解压安装程序,这些安装程序几年前已作为shell脚本实现。这些脚本包含几行可执行的Shell命令,然后是这些命令读取的巨大数据块。因此,我假设这样做是为了使bash不必将整个脚本读入内存,而这对于大型的自解压脚本是不起作用的。
Martin von Wittich 2014年

下面是一个自解压脚本的例子:ftp.games.skynet.be/pub/wolfenstein/...
马丁·冯·Wittich

真好!我记得看到这些自安装脚本,AMD Catalyst驱动程序(专有)仍然像这样发货,它会自解压然后安装。这些取决于这种读取块中文件的行为。谢谢你的例子!
aularon 2014年

Answers:


12

这是设计使然。Bash会分块读取脚本。因此,它将读取脚本的一部分,运行它可以运行的任何行,然后读取下一个块。

所以您遇到了这样的事情:

  • Bash读取脚本的前256个字节(字节0-255)。
  • 在前256个字节中,有一条命令需要一段时间才能运行,bash启动该命令,等待其退出。
  • 当命令运行时,脚本将更新,并且已更改的部分已读取的256个字节之后
  • 当bash命令运行结束时,它将继续读取文件,从原来的位置恢复,得到256-511字节。
  • 脚本的那部分改变了,但是bash并不知道。

问题变得更加棘手的是,如果您在字节256之前进行了任何编辑。可以说您删除了几行。然后,脚本中位于字节256处的数据现在位于其他位置,例如位于字节156(早于100字节)。因此,当bash继续阅读时,它将获得原始的356。

这只是一个例子。Bash不必一次读取256个字节。我不知道一次读取多少,但没关系,行为仍然相同。


不,它按块读取,但是倒退到要确保在读取前一个命令后仍按原样读取下一个命令的位置。
斯特凡Chazelas

@StephaneChazelas你从哪里得到的?我只是做了一个跟踪,甚至stat没有文件来查看它是否已更改。没有lseek电话
Patrick

有关strace输出的相关部分,请参见pastie.org/8662761。看看在期间如何echo foo更改为。早在版本2以来就一直具有这种行为,所以我认为这不是版本问题。echo barsleep
斯特凡Chazelas

显然,它逐行读取文件,我尝试了该文件,然后发现了该文件。我将编辑问题以突出显示该行为。
2014年

@Patrick,如果您可以更新您的答案以反映它正在逐行阅读,那么我可以接受您的答案。(检查我对此问题的编辑)。
2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.