У меня есть общий вопрос, который может быть результатом неправильного понимания того, как процессы обрабатываются в Linux.
Для моих целей я собираюсь определить «скрипт» как фрагмент кода bash, сохраненного в текстовом файле с разрешениями на выполнение, активированными для текущего пользователя.
У меня есть серия сценариев, которые вызывают друг друга в тандеме. Для простоты я назову их сценариями A, B и C. Сценарий A выполняет серию операторов и затем делает паузу, затем выполняет сценарий B, затем делает паузу, затем выполняет сценарий C. Другими словами, серия шагов это что-то вроде этого:
Запустите сценарий A:
- Серия заявлений
- Пауза
- Запустить скрипт B
- Пауза
- Запустить скрипт C
По своему опыту я знаю, что если я буду запускать сценарий A до первой паузы, а затем вносить изменения в сценарий B, эти изменения отражаются в выполнении кода, когда я позволяю ему возобновить работу. Аналогичным образом, если я внесу изменения в сценарий C, пока сценарий A все еще приостановлен, а затем разрешу его продолжить после сохранения изменений, эти изменения будут отражены в выполнении кода.
Вот реальный вопрос, есть ли способ отредактировать скрипт A, пока он еще работает? Или редактирование невозможно после начала его выполнения?
источник
Ответы:
В Unix большинство редакторов работают, создавая новый временный файл, содержащий отредактированное содержимое. Когда отредактированный файл сохранен, исходный файл удаляется, а временный файл переименовывается в исходное имя. (Конечно, существуют различные меры предосторожности для предотвращения потери данных.) Это, например, стиль, используемый
sed
илиperl
когда вызывается с-i
флагом («на месте»), который на самом деле вообще не «на месте». Надо было называть «новое место со старым именем».Это хорошо работает, потому что Unix гарантирует (по крайней мере для локальных файловых систем), что открытый файл продолжает существовать до тех пор, пока он не будет закрыт, даже если он «удален» и создан новый файл с тем же именем. (Это не случайно, что системный вызов unix для «удаления» файла фактически называется «unlink».) Так что, вообще говоря, если интерпретатор оболочки имеет открытый исходный файл, и вы «редактируете» файл, как описано выше оболочка даже не увидит изменений, так как у нее по-прежнему открыт исходный файл.
[Примечание: как и во всех комментариях, основанных на стандартах, вышеизложенное допускает многократное толкование, и существуют различные примеры, такие как NFS. Педанты могут заполнить комментарии исключениями.]
Конечно, можно напрямую изменять файлы; это просто не очень удобно для целей редактирования, потому что, хотя вы можете перезаписывать данные в файле, вы не можете удалять или вставлять без смещения всех следующих данных, что повлечет за собой довольно много переписывания. Более того, пока вы выполняете это смещение, содержимое файла будет непредсказуемым, и пострадают процессы, у которых этот файл открыт. Чтобы избежать этого (как, например, в системах баз данных), вам необходим сложный набор протоколов модификации и распределенных блокировок; материал, который выходит за рамки типичной утилиты редактирования файлов.
Итак, если вы хотите редактировать файл во время его обработки оболочкой, у вас есть два варианта:
Вы можете добавить в файл. Это всегда должно работать.
Вы можете перезаписать файл новым содержимым точно такой же длины . Это может или не может работать, в зависимости от того, оболочка уже прочитала эту часть файла или нет. Поскольку большая часть файлового ввода-вывода включает в себя буферы чтения, и поскольку все известные мне оболочки читают целую составную команду перед ее выполнением, маловероятно, что вам это сойдет с рук. Это конечно не будет надежно.
Я не знаю ни одной формулировки в стандарте Posix, которая фактически требует возможности добавления к файлу сценария во время его выполнения, так что он может работать не с каждой оболочкой, совместимой с Posix, тем более с текущим предложением почти и иногда послеродовые оболочки. Итак, YMMV. Но, насколько я знаю, он действительно надежно работает с Bash.
В качестве доказательства приведем реализацию «без петель» печально известной программы на 99 бутылок пива в bash, которая используется
dd
для перезаписи и добавления (перезапись предположительно безопасна, поскольку она заменяет текущую исполняемую строку, которая всегда является последней строкой файл с комментарием точно такой же длины; я сделал это для того, чтобы конечный результат мог быть выполнен без изменения поведения.)источник
export beer=100
до запуска сценария, он работает как ожидалось.bash
проходит долгий путь, чтобы убедиться, что он читает команды непосредственно перед их выполнением.Например, в:
Оболочка будет читать сценарий по блокам, поэтому, скорее всего, прочитает обе команды, интерпретирует первую, а затем обратится к концу
cmd1
сценария и снова прочитает сценарий, чтобы прочитатьcmd2
и выполнить его.Вы можете легко проверить это:
(хотя, глядя на
strace
результаты этого, кажется, что он делает некоторые более причудливые вещи (например, читает данные несколько раз, выполняет поиск назад ...), чем когда я пытался сделать то же самое несколько лет назад, так что моё утверждение выше об обратном больше не распространяется на новые версии).Однако, если вы напишите свой скрипт как:
Оболочка должна будет прочитать до закрытия
}
, сохранить это в памяти и выполнить его. Из-за этогоexit
оболочка не будет снова читать сценарий, поэтому вы можете безопасно редактировать его, пока оболочка интерпретирует его.В качестве альтернативы, при редактировании сценария убедитесь, что вы написали новую копию сценария. Оболочка будет продолжать читать исходную (даже если она удалена или переименована).
Чтобы сделать это, переименовать
the-script
вthe-script.old
и копироватьthe-script.old
вthe-script
и редактировать его.источник
На самом деле не существует безопасного способа изменить скрипт во время его работы, потому что оболочка может использовать буферизацию для чтения файла. Кроме того, если сценарий изменяется путем замены его новым файлом, оболочки обычно будут читать новый файл только после выполнения определенных операций.
Часто, когда сценарий изменяется во время выполнения, оболочка заканчивает тем, что сообщает о синтаксических ошибках. Это связано с тем, что, когда оболочка закрывает и снова открывает файл сценария, она использует смещение байтов в файле, чтобы изменить положение при возврате.
источник
Вы можете обойти это, установив ловушку на вашем скрипте, а затем используя,
exec
чтобы подобрать новое содержимое скрипта. Обратите внимание, однако, чтоexec
вызов запускает скрипт с нуля, а не с того места, где он был достигнут в процессе выполнения, и поэтому будет вызван скрипт B (и так далее).Это будет продолжать отображать дату на экране. Затем я мог бы отредактировать свой сценарий и изменить
date
наecho "Date: $(date)"
. При записи этого работающего скрипта все равно просто отображается дата. Однако, если я отправлю сигнал, который я установилtrap
для захвата, сценарий будетexec
(заменяет текущий запущенный процесс указанной командой), который является командой$CMD
и аргументами$@
. Вы можете сделать это, введяkill -1 PID
- где PID - это PID запущенного скрипта - и выходные данные изменятся, чтобы показатьDate:
передdate
выводом команды.Вы можете сохранить «состояние» вашего скрипта во внешнем файле (например, в / tmp) и прочитать его содержимое, чтобы узнать, где «возобновить» выполнение программы при повторном выполнении. Затем можно добавить дополнительное завершение прерываний (SIGINT / SIGQUIT / SIGKILL / SIGTERM), чтобы очистить этот файл tmp, чтобы при перезапуске после прерывания «Сценария A» он начинался с самого начала. Версия с состоянием будет что-то вроде:
источник
$0
и$@
в начале скрипта и используяexec
вместо этого эти переменные .