在运行时编辑shell脚本

118

您能在脚本运行时编辑它并使更改影响正在运行的脚本吗?

我对一个csh脚本有兴趣,该脚本批量运行多种构建模式,并在整夜运行。如果我想到了什么,我希望能够添加其他命令或注释掉未执行的命令。

如果不可能,请问是否有任何shell或批处理机制可以让我这样做?

当然,我已经尝试过了,但是在几个小时后才能看到它是否有效,而我对幕后发生的事情很好奇。


3
我曾经尝试编辑正在运行的脚本文件,发现有两种结果:1)更改会被忽略,好像已经将整个文件读入内存;2)脚本会崩溃并显示错误,好像只读取了命令的一部分。我不知道这是否取决于脚本的大小。无论如何,我不建议尝试。 - Paul Tomblin
简而言之:不行,除非它是自我引用/调用,此时主脚本仍将是旧的。 - Wrikken
这里有两个重要的问题。1)我如何正确且安全地向正在运行的脚本添加命令?2)当我修改正在运行的脚本时,会发生什么? - Chris Quenelle
7
一个问题是shell在执行脚本时是读取整个脚本文件然后再执行,还是在执行时逐步读取。我不知道具体情况,可能也没有明确规定。你应该避免依赖其中任何一种行为。 - Keith Thompson
11个回答

61

至少在我的环境中,它确实会影响 bash,而且是以非常不愉快的方式。请看以下代码。首先是a.sh


#!/bin/sh

echo "First echo"
read y

echo "$y"

echo "That's all."

b.sh:

#!/bin/sh

echo "First echo"
read y

echo "Inserted"

echo "$y"

# echo "That's all."

$ cp a.sh run.sh
$ ./run.sh
$ # open another terminal
$ cp b.sh run.sh  # while 'read' is in effect
$ # Then type "hello."

在我的情况下,输出始终是:

hello
hello
That's all.
That's all.

(当然自动化处理远比这个示例更好,但上述示例易于阅读。)

[编辑] 这是不可预测的,因此存在危险。 最佳解决方法是如此描述把所有内容都放在括号里,在结束括号之前,加上"exit"仔细阅读链接的答案以避免陷阱。

[添加] 精确的行为取决于额外的换行符,也可能取决于您的Unix版本、文件系统等。如果您只想查看一些影响,请在“read”行之前和/或之后在b.sh中添加“echo foo/bar”。


3
哦,我没有看到感情。我有什么遗漏吗? - user unknown
具体行为取决于一个额外的换行符,也许还取决于Unix版本、文件系统等,但并不确定。如果您只是想看到任何影响,请通过添加10行echo foo/bar/baz来扩大b.sh。dave4220和我回答的要点是这种影响很难预测。(顺便说一下,“affection”这个名词的意思是“爱”=) - teika kazura
是的,它非常破碎。我有一个解决方案(如下)。更危险的是svn/rsync/git更新。 - Erik Aronesty

51

试试这个...创建一个名为bash-is-odd.sh的文件:

#!/bin/bash
echo "echo yes i do odd things" >> bash-is-odd.sh

这证明bash确实是“随时解释”脚本的。编辑正在运行的长脚本会导致不可预测的结果,如插入随机字符等。为什么?因为bash从最后一个字节位置读取,所以编辑会移动当前正在读取的字符位置。

一句话,由于这个“特性”,Bash非常、非常不安全。当与bash脚本一起使用时,svn和rsync尤其令人烦恼,因为它们默认情况下会“合并”结果...直接就地编辑。rsync有一个可以解决这个问题的模式,而svn和git则没有。

我提供了一个解决方案。创建一个名为/bin/bashx的文件:

#!/bin/bash
source "$1"

现在在你的脚本中使用#!/bin/bashx,并始终使用bashx而不是bash运行它们。这样可以解决问题-您可以安全地使用rsync同步您的脚本。

@AF7提出/测试的另一种(内联)解决方案:

{
   # your script
exit $?
} 

花括号保护免于编辑,exit命令保护免于追加。当然,如果bash带有一个选项,比如-w(整个文件),或者其他可以达到这个效果的东西,那么我们所有人都会更好。


1
顺便说一句,这是一个加号来抵消减号,也因为我喜欢你编辑过的答案。 - Andrew Barber
2
我不能推荐这个。在这种解决方法中,位置参数会向后移动一个位置。还要记住,您不能将值分配给 $0。这意味着如果您仅仅将“/bin/bash”更改为“/bin/bashx”,则许多脚本会失败。 - teika kazura
2
请告诉我这个选项已经被实现了! - AF7
12
一个简单的解决方法,是由我的朋友朱利奥给我建议的(功劳归于他),就是在脚本开头插入{,并在结尾处插入}。这样Bash会被强制将所有内容读入内存中。 - AF7
2
@korkman 是的,我同意,这就是为什么 bashx 肯定可以解决问题,但像 {...}; exit $? 这样的解决方案可能存在竞争条件,即文件在代码(安全的部分)和退出命令(不安全的部分)之间被编辑。我认为你必须将退出命令放在花括号中以确保安全。 - Erik Aronesty
显示剩余7条评论

18

将你的脚本分成函数,并每次调用一个函数时从单独的文件中source它。然后,您可以随时编辑文件,运行中的脚本在下一次获取源时会捕捉到这些更改。

foo() {
  source foo.sh
}
foo

我已经成功地使用这种技术一段时间来更新我的长时间运行的构建脚本。我很想学习一种技术,使当前文件读取到文件结尾,这样我就不必为每个shell脚本实现两个文件了。 - Chris Quenelle

5

好问题!希望这个简单的脚本能够帮到您。

#!/bin/sh
echo "Waiting..."
echo "echo \"Success! Edits to a .sh while it executes do affect the executing script! I added this line to myself during execution\"  " >> ${0}
sleep 5
echo "When I was run, this was the last line"

在Linux下,如果你打字速度够快,那么对正在运行的.sh文件进行的更改将由执行脚本来实施!


3
一个有趣的副作用 - 如果你正在运行一个Python脚本,它是不会改变的。(对于那些了解shell如何运行Python脚本的人来说,这可能非常显然,但我认为对于寻找此功能的人来说,这可能是一个有用的提醒。)
我创建了:
#!/usr/bin/env python3
import time
print('Starts')
time.sleep(10)
print('Finishes unchanged')

然后在另一个 shell 中,在此期间保持休眠状态,编辑最后一行。当此操作完成时,它会显示未更改的行,可能是因为正在运行一个 .pyc 文件?在 Ubuntu 和 macOS 上都发生了同样的情况。


2
Python通常在运行脚本之前会读取并编译整个脚本,因为这样做。有些shell也是这样做的,有些则逐段读取和执行脚本。只有在确保对特定语言实现安全的情况下,才能修改正在运行的脚本。 - user1934428

1

我没有安装csh,但是

#!/bin/sh
echo Waiting...
sleep 60
echo Change didn't happen

运行它,快速编辑最后一行以读取。
echo Change happened

输出结果为

Waiting...
/home/dave/tmp/change.sh: 4: Syntax error: Unterminated quoted string

嗯。

我猜对shell脚本的编辑只有在重新运行后才会生效。


2
你应该将想要显示的字符串放在引号中。 - user1463308
3
实际上,这证明了您的编辑器并不像您想象的那样工作。许多编辑器(包括vim、emacs)都是在“tmp”文件上操作,而不是实时文件。尝试使用“echo 'echo uh oh' >> myshell.sh”而不是vi/emacs...看看它输出新内容的情况。更糟糕的是...svn和rsync也是这样编辑的! - Erik Aronesty
5
这个错误与正在编辑的文件无关:这是因为你使用了一个撇号!它被视为单引号,导致出现错误。请将整个字符串用双引号括起来,然后再试一遍。 - Anonymous Penguin
7
出现错误表明编辑没有达到预期效果。 - danmcardle
@danmcardle 谁知道呢?也许bash看到了Change didn'ned - Kirill Bulygin

1
如果这全部在一个脚本中,那么不行。但是,如果你将它设置为调用子脚本的驱动脚本,那么你可能能够在它被调用之前或在循环时再次调用之前更改子脚本,在这种情况下,我相信这些更改会反映在执行中。

0

我听到了...但是如果加上一些间接引用呢:

BatchRunner.sh

Command1.sh
Command2.sh

Command1.sh

runSomething

Command2.sh

runSomethingElse

那么在BatchRunner运行之前,您应该能够编辑每个命令文件的内容,对吗?

或者

更清晰的版本将使BatchRunner查找一个单一的文件,在其中逐行连续运行。然后,您应该能够在第一个文件正在运行时编辑这个第二个文件,对吗?


我想知道它是否将它们加载到内存中运行,一旦主进程启动,更改就无关紧要了... - Eric Hodonsky

-1

使用Zsh代替进行脚本编写。

据我所知,Zsh不会表现出这种令人沮丧的行为。


这是选择Zsh而不是bash的第473个理由。最近我一直在处理一个旧的bash脚本,它需要10分钟才能运行完,而我却不能在等待期间进行编辑! - Micah Elliott

-5
通常情况下,在脚本运行时进行编辑是不常见的。您需要为操作设置控制检查。使用if/else语句来检查条件。如果某些操作失败,则执行这个,否则执行那个。这就是正确的方法。

实际上,这与脚本失败的问题不太相关,而是决定在批处理作业运行过程中进行修改。例如,意识到我想要编译更多内容,或者我不需要已经在队列中的某些作业。 - ack
1
如果你严格追加到脚本中,那么bash将会按照你的期望执行! - Erik Aronesty

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接