Bash автоматически перезагружает (внедряет) обновления в работающий скрипт после его сохранения: почему? Любое практическое использование?

10

Я писал сценарий bash и случайно обновил код (сохранил файл сценария на диске), пока сценарий ожидал некоторого ввода в whileцикле. После того, как я вернулся в терминал и продолжил предыдущий вызов скрипта, bash выдал ошибку о синтаксисе файла:

/home/aularon/bin/script: line 58: unexpected EOF while looking for matching `"'
/home/aularon/bin/script: line 67: syntax error: unexpected end of file

Поэтому я попытался сделать следующее:

1-й: создайте скрипт, self-update.shдавайте назовем его:

#!/bin/bash
fname=$(mktemp)
cat $0 | sed 's/BEFORE\./AFTER!./' > $fname
cp $fname $0
rm -f $fname
echo 'String: BEFORE.';

Сценарий читает код, изменяя слово «ДО» на «ПОСЛЕ», а затем переписывает себя с новым кодом.

2-ой Запустите это:

chmod +x self-update.sh
./self-update.sh

3-е чудо ...

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.

Я бы даже не догадался, что при одном и том же вызове он выдаст ПОСЛЕ! На втором запуске точно, но не на первом.

Итак, мой вопрос: это намеренно (по замыслу)? или это из-за того, как bash запускает скрипт? Строка за строкой или команда за командой. Есть ли польза от такого поведения? Любой пример этого?


Редактировать: я попытался переформатировать файл, чтобы поместить все команды в одну строку, он не работает сейчас:

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;echo 'String: BEFORE.';

Вывод:

aularon@aularon-laptop:~$ ./self-update.sh #First invocation
String: BEFORE.
aularon@aularon-laptop:~$ ./self-update.sh #Second invocation
String: AFTER!.

При перемещении echoстроки на следующую строку, отделив ее от cpвызова rewriting ( ):

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;
echo 'String: BEFORE.';

И теперь это работает снова:

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.
aularon
источник
1
Связанный: stackoverflow.com/questions/4754592/…
Мартин фон Виттих
1
Я просмотрел источник, но не могу найти никакого объяснения этому. Я уверен, что видел несколько самораспаковывающихся инсталляторов, которые были реализованы в виде сценариев оболочки несколько лет назад. Эти сценарии содержат несколько строк исполняемых команд оболочки, а затем огромный блок данных, который читается этими командами. Поэтому я предполагаю, что это разработано таким образом, что bash не нужно считывать весь скрипт в память, которая не будет работать с огромными самораспаковывающимися скриптами.
Мартин фон Виттих
Вот пример сценария SFX: ftp.games.skynet.be/pub/wolfenstein/…
Мартин фон
Ницца! Я помню, как видел эти самоустанавливающиеся сценарии. Драйвер AMD Catalyst (проприетарный) по-прежнему поставляется так: он самораспаковывается и затем устанавливает. Это зависит от такого поведения чтения файлов по частям. Спасибо за пример!
Aularon

Ответы:

12

Это по замыслу. Bash читает сценарии кусками. Таким образом, он будет читать часть скрипта, запускать любые возможные строки и затем читать следующий фрагмент.

Итак, вы сталкиваетесь с чем-то вроде этого:

  • Bash читает первые 256 байтов (байты 0-255) скрипта.
  • В этих первых 256 байтах находится команда, выполнение которой занимает некоторое время, и bash запускает эту команду, ожидая ее завершения.
  • Во время выполнения команды сценарий обновляется, и часть изменяется после 256 байтов, которые он уже прочитал.
  • Когда команда bash была выполнена, она завершает чтение файла, возобновляя с того места, где она находилась, получая байты 256-511.
  • Эта часть сценария изменилась, но bash этого не знает.

Еще более проблематичным становится то, что если вы редактируете что-либо до байта 256. Допустим, вы удалили пару строк. Тогда данные в сценарии, которые были в байте 256, теперь находятся где-то еще, скажем, в байте 156 (на 100 байтов ранее). Из-за этого, когда bash продолжит читать, он получит то, что изначально было 356.

Это всего лишь пример. Bash не обязательно читает 256 байтов за раз. Я не знаю точно, сколько он читает за раз, но это не имеет значения, поведение остается прежним.

Патрик
источник
Нет, он читает по частям, но перематывает туда, где он должен был быть уверен, что прочитал следующую команду, какой она есть, после того, как предыдущая команда вернулась.
Стефан Шазелас
@StephaneChazelas Откуда ты это взял? Я только что сделал strace, и это не так, как даже statфайл, чтобы увидеть, изменился ли он. Нет lseekзвонков.
Патрик
Смотрите pastie.org/8662761 для соответствующих частей вывода strace. Посмотрите, как echo fooизменилось echo barво время sleep. Это ведет себя так же, как в версии 2, так что я не думаю, что это проблема версии.
Стефан Шазелас
Видимо он читает файл построчно, я пытался с файлом, и это то, что я узнал. Я отредактирую свой вопрос, чтобы подчеркнуть это поведение.
Aularon
@ Патрик, если вы можете обновить свой ответ, чтобы отразить его, он читает построчно, поэтому я могу принять ваш ответ. (Проверьте мою правку на вопрос по этому вопросу).
Aularon