У Unix більшість редакторів працюють, створюючи новий тимчасовий файл, що містить відредагований вміст. Коли відредагований файл буде збережено, вихідний файл видаляється, а тимчасовий файл перейменовується на оригінальне ім'я. (Звичайно, існують різні гарантії для запобігання втраті даних.) Це, наприклад, стиль, який використовується sed
або perl
коли викликається -i
прапором ("на місці"), який насправді взагалі не "на місці". Його слід було назвати "новим місцем зі старою назвою".
Це добре працює, тому що unix запевняє (принаймні, для локальних файлових систем), що відкритий файл продовжує існувати до його закриття, навіть якщо він "видалений" та створений новий файл з тим же ім'ям. (Не випадково виклик системи Unix для "видалення" файлу насправді називається "від’єднати".) Отже, загалом кажучи, якщо інтерпретатор оболонки відкриває якийсь вихідний файл, і ви "редагуєте" файл у спосіб, описаний вище , оболонка навіть не побачить зміни, оскільки в неї все ще відкритий оригінальний файл.
[Примітка: як і всі коментарі, що базуються на стандартах, вищезазначене піддається численним тлумаченням, і існують різні випадкові випадки, такі як NFS. Педани можуть залишити коментарі винятками.]
Звичайно, можна безпосередньо змінювати файли; це просто не дуже зручно для редагування, оскільки, хоча ви можете перезаписати дані у файл, ви не можете видалити або вставити, не змістивши всі наступні дані, що означало б дуже багато перезапису. Крім того, поки ви робили цю зміну, вміст файлу був би непередбачуваним, і процеси, які мали відкритий файл, зазнали б погіршення. Щоб уникнути цього (як, наприклад, у системах баз даних), вам потрібен складний набір протоколів модифікації та розподілених замків; матеріал, що виходить за рамки типової утиліти редагування файлів.
Отже, якщо ви хочете відредагувати файл, обробляючи його оболонкою, у вас є два варіанти:
Ви можете додати файл. Це завжди має працювати.
Ви можете перезаписати файл новим вмістом точно такої ж довжини . Це може чи не може працювати, залежно від того, оболонка вже прочитала цю частину файлу чи ні. Оскільки більшість файлових вводу-виводу включає буфери зчитування, а оскільки всі знаходяться мені оболонки читають цілу складову команду перед її виконанням, навряд чи ви зможете уникнути цього. Це, безумовно, не було б надійним.
Я не знаю жодного формулювання стандарту Posix, яке насправді вимагає можливості додавання до файлу сценарію під час виконання файлу, тому він може працювати не з усіма сумісними оболонками Posix, тим більше з поточною пропозицією майже - а іноді-сумісні з оболонкою оболонок. Так YMMV. Але наскільки я знаю, це працює надійно з 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} #