在Unix中,大多数编辑器通过创建一个包含已编辑内容的新临时文件来工作。保存编辑后的文件后,将删除原始文件,并将临时文件重命名为原始名称。(当然,有各种防止数据丢失的保护措施。)例如,这是(sed
或实际上)不是“就地”标志perl
由-i
(“就地”)标志调用时使用的样式。它应该被称为“旧名称的新地方”。
这是行之有效的,因为unix确保(至少对于本地文件系统而言)打开的文件一直存在,直到关闭为止,即使该文件被“删除”并创建了一个具有相同名称的新文件。(实际上,unix系统对“删除”文件的调用实际上被称为“ unlink”。)因此,通常来说,如果外壳解释器打开了某些源文件,并且您以上述方式“编辑”该文件,由于它仍然打开了原始文件,因此外壳什至看不到更改。
[注:与所有基于标准的注释一样,以上内容可能有多种解释,并且存在各种不同的情况,例如NFS。欢迎学友在注释中添加例外。]
当然,可以直接修改文件。这对于编辑目的不是很方便,因为尽管您可以覆盖文件中的数据,但是如果不移动所有后续数据就无法删除或插入,这意味着需要进行大量重写。此外,当您执行此转换时,文件的内容将是不可预测的,打开文件的进程将受到影响。为了避免这种情况(例如,与数据库系统一样),您需要一套复杂的修改协议和分布式锁。这些东西远远超出了典型文件编辑实用程序的范围。
因此,如果要在外壳处理文件的同时编辑文件,则有两个选择:
您可以追加到文件。这应该一直有效。
您可以使用长度完全相同的新内容覆盖文件。根据外壳程序是否已经读取文件的那部分,这可能行不通。由于大多数文件I / O都涉及读取缓冲区,并且由于我所知道的所有外壳程序在执行前都读取了整个复合命令,因此您几乎不可能摆脱这种情况。这肯定是不可靠的。
我不知道Posix标准中的任何措辞,实际上这要求在执行文件时将其附加到脚本文件中,因此它可能不适用于每个符合Posix的Shell,而对于当前提供的几乎几乎所有内容来说,则更是如此。有时是posix兼容的shell。YMMV。但据我所知,它确实可以与bash一起使用。
作为证据,这是臭名昭著的99瓶bash啤酒程序的“无循环”实现,该程序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} #