В Unix большинство редакторов работают, создавая новый временный файл, содержащий отредактированное содержимое. Когда отредактированный файл сохранен, исходный файл удаляется, а временный файл переименовывается в исходное имя. (Конечно, существуют различные меры предосторожности для предотвращения потери данных.) Это, например, стиль, используемый sed
или perl
когда вызывается с -i
флагом («на месте»), который на самом деле вообще не «на месте». Надо было называть «новое место со старым именем».
Это хорошо работает, потому что Unix гарантирует (по крайней мере для локальных файловых систем), что открытый файл продолжает существовать до тех пор, пока он не будет закрыт, даже если он «удален» и создан новый файл с тем же именем. (Это не случайно, что системный вызов unix для «удаления» файла фактически называется «unlink».) Так что, вообще говоря, если интерпретатор оболочки имеет открытый исходный файл, и вы «редактируете» файл, как описано выше оболочка даже не увидит изменений, так как у нее по-прежнему открыт исходный файл.
[Примечание: как и во всех комментариях, основанных на стандартах, вышеизложенное допускает многократное толкование, и существуют различные примеры, такие как NFS. Педанты могут заполнить комментарии исключениями.]
Конечно, можно напрямую изменять файлы; это просто не очень удобно для целей редактирования, потому что, хотя вы можете перезаписывать данные в файле, вы не можете удалять или вставлять без смещения всех следующих данных, что повлечет за собой довольно много переписывания. Более того, пока вы выполняете это смещение, содержимое файла будет непредсказуемым, и пострадают процессы, у которых этот файл открыт. Чтобы избежать этого (как, например, в системах баз данных), вам необходим сложный набор протоколов модификации и распределенных блокировок; материал, который выходит за рамки типичной утилиты редактирования файлов.
Итак, если вы хотите редактировать файл во время его обработки оболочкой, у вас есть два варианта:
Вы можете добавить в файл. Это всегда должно работать.
Вы можете перезаписать файл новым содержимым точно такой же длины . Это может или не может работать, в зависимости от того, оболочка уже прочитала эту часть файла или нет. Поскольку большая часть файлового ввода-вывода включает в себя буферы чтения, и поскольку все известные мне оболочки читают целую составную команду перед ее выполнением, маловероятно, что вам это сойдет с рук. Это конечно не будет надежно.
Я не знаю ни одной формулировки в стандарте Posix, которая фактически требует возможности добавления к файлу сценария во время его выполнения, так что он может работать не с каждой оболочкой, совместимой с Posix, тем более с текущим предложением почти и иногда послеродовые оболочки. Итак, 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} #