如果在执行期间编辑脚本会发生什么?

Caf*_*eur 37 linux shell bash process-management

我有一个一般性问题,这可能是由于对 Linux 中进程的处理方式的误解所致。

出于我的目的,我将定义一个“脚本”作为保存到文本文件的 bash 代码片段,并为当前用户启用执行权限。

我有一系列相互调用的脚本。为简单起见,我将它们称为脚本 A、B 和 C。脚本 A 执行一系列语句,然后暂停,然后执行脚本 B,然后暂停,然后执行脚本 C。换句话说,该系列步骤是这样的:

运行脚本A:

  1. 系列声明
  2. 暂停
  3. 运行脚本 B
  4. 暂停
  5. 运行脚本 C

我从经验中知道,如果我运行脚本 A 直到第一次暂停,然后在脚本 B 中进行编辑,当我允许它恢复时,这些编辑会反映在代码的执行中。同样,如果我在脚本 A 仍然暂停时对脚本 C 进行编辑,然后在保存更改后允许它继续,这些更改将反映在代码的执行中。

那么真正的问题是,有没有办法在脚本 A 仍在运行时对其进行编辑?或者一旦开始执行就无法编辑?

ric*_*ici 24

在 Unix 中,大多数编辑器通过创建一个包含编辑内容的新临时文件来工作。保存编辑后的文件时,将删除原始文件,并将临时文件重命名为原始名称。(当然,有各种保护措施可以防止数据丢失。)例如,这是由("in-place") 标志使用sedperl调用时使用的样式-i,这根本不是真正的"就地"。它应该被称为“旧名的新地方”。

这很有效,因为 unix 确保(至少对于本地文件系统)打开的文件在关闭之前一直存在,即使它被“删除”并创建了一个具有相同名称的新文件。(用于“删除”文件的 unix 系统调用实际上称为“取消链接”并非巧合。)因此,一般来说,如果 shell 解释器打开了某个源文件,并且您以上述方式“编辑”该文件,外壳甚至不会看到更改,因为它仍然打开原始文件。

[注意:与所有基于标准的评论一样,以上内容有多种解释,并且存在各种极端情况,例如 NFS。欢迎学究们填写评论,但有例外。]

当然,也可以直接修改文件;只是为了编辑目的不是很方便,因为虽然您可以覆盖文件中的数据,但如果不移动所有后续数据,则无法删除或插入,这意味着需要大量重写。此外,当您进行这种转换时,文件的内容将是不可预测的,并且打开文件的进程会受到影响。为了摆脱这种情况(例如,与数据库系统一样),您需要一套复杂的修改协议和分布式锁;远远超出典型文件编辑实用程序范围的东西。

因此,如果您想在 shell 处理文件时对其进行编辑,您有两个选择:

  1. 您可以附加到文件。这应该始终有效。

  2. 您可以使用长度完全相同的新内容覆盖该文件。这可能会也可能不会起作用,这取决于 shell 是否已经读取了文件的那部分。由于大多数文件 I/O 都涉及读取缓冲区,而且由于我所知道的所有 shell 在执行之前都会读取整个复合命令,因此您不太可能摆脱这种情况。肯定是不靠谱的。

我不知道 Posix 标准中的任何措辞实际上需要在执行文件时附加到脚本文件的可能性,因此它可能不适用于每个 Posix 兼容 shell,更不用说当前提供的几乎 -有时是符合 posix 的 shell。所以YMMV。但据我所知,它确实可以与 bash 一起可靠地工作。

作为证据,这是 bash 中臭名昭著的 99 瓶啤酒程序的“无循环”实现,它用于dd覆盖和追加(覆盖可能是安全的,因为它替换了当前正在执行的行,该行始终是文件,带有完全相同长度的注释;我这样做是为了可以在没有自我修改行为的情况下执行最终结果。)

#!/bin/bash
if [[ $1 == reset ]]; then
  printf "%s\n%-16s#\n" '####' 'next ${1:-99}' |
  dd if=/dev/stdin of=$0 seek=$(grep -bom1 ^#### $0 | cut -f1 -d:) bs=1 2>/dev/null
  exit
fi

step() {
  s=s
  one=one
  case $beer in
    2) beer=1; unset s;;
    1) beer="No more"; one=it;;
    "No more") beer=99; return 1;;
    *) ((--beer));;
  esac
}
next() {
  step ${beer:=$(($1+1))}
  refrain |
  dd if=/dev/stdin of=$0 seek=$(grep -bom1 ^next\  $0 | cut -f1 -d:) bs=1 conv=notrunc 2>/dev/null
}
refrain() {
  printf "%-17s\n" "# $beer bottles"
  echo echo ${beer:-No more} bottle$s of beer on the wall, ${beer:-No more} bottle$s of beer.
  if step; then
    echo echo Take $one down, pass it around, $beer bottle$s of beer on the wall.
    echo echo
    echo next abcdefghijkl
  else
    echo echo Go to the store, buy some more, $beer bottle$s of beer on the wall.
  fi
}
####
next ${1:-99}   #
Run Code Online (Sandbox Code Playgroud)


Sté*_*las 21

bash 确保它在执行命令之前读取命令有很长的路要走。

例如在:

cmd1
cmd2
Run Code Online (Sandbox Code Playgroud)

shell 将按块读取脚本,因此可能读取两个命令,解释第一个命令,然后返回到cmd1脚本的末尾并再次读取脚本以读取cmd2和执行它。

您可以轻松验证它:

$ cat a
echo foo | dd 2> /dev/null bs=1 seek=50 of=a
echo bar
$ bash a
foo
Run Code Online (Sandbox Code Playgroud)

(虽然查看strace输出,它似乎做了一些更花哨的事情(比如多次读取数据,回溯......)不再适用于较新的版本)。

但是,如果您将脚本编写为:

{
  cmd1
  cmd2
  exit
}
Run Code Online (Sandbox Code Playgroud)

shell 必须读取直到结束},将其存储在内存中并执行它。由于exit,shell 将不会再次读取脚本,因此您可以在 shell 解释它时安全地编辑它。

或者,在编辑脚本时,请确保编写脚本的新副本。shell 将继续读取原始的(即使它被删除或重命名)。

为此,请重命名the-scriptthe-script.old并复制the-script.oldthe-script并编辑它。


ash*_*ash 6

确实没有安全的方法可以在脚本运行时修改脚本,因为 shell 可以使用缓冲来读取文件。此外,如果通过用新文件替换脚本来修改脚本,shell 通常只会在执行某些操作后才读取新文件。

通常,当脚本在执行时发生更改时,shell 最终会报告语法错误。这是因为当 shell 关闭并重新打开脚本文件时,它使用文件中的字节偏移量在返回时重新定位自身。