Что произойдет, если вы отредактируете скрипт во время выполнения?

У меня есть общий вопрос, который может быть результатом непонимания процессов обработки в Linux.

В моих целях я собираюсь определить «скрипт» как фрагмент кода bash, сохраненный в текстовом файле с разрешениями на выполнение для текущего пользователя.

У меня есть серия скриптов, которые называют друг друга в тандеме. Для простоты я буду называть их сценариями A, B и C. Сценарий A выполняет серию утверждений, а затем приостанавливает, затем выполняет скрипт B, затем он приостанавливается, затем выполняет сценарий C. Другими словами, серия шагов выглядит примерно так:

Сценарий запуска A:

  1. Серия операторов
  2. Пауза
  3. Run Script B
  4. Пауза
  5. Сценарий запуска C

Я знаю по опыту, что если я запустил сценарий A до первой паузы, а затем сделаю изменения в скрипте B, эти изменения будут отражены в выполнении кода, когда я разрешу его возобновить. Аналогично, если я редактирую сценарий C, пока сценарий A все еще приостановлен, затем разрешите продолжить его после сохранения изменений, эти изменения отражаются при выполнении кода.

Вот реальный вопрос, есть ли способ редактировать Script A, пока он все еще работает? Или редактирование невозможно после его выполнения?

27 голосов | спросил CaffeineConnoisseur 28 AM00000070000000831 2013, 07:37:08

4 ответа


19

В Unix большинство редакторов работают, создавая новый временный файл, содержащий отредактированное содержимое. Когда отредактированный файл сохраняется, исходный файл удаляется, а временный файл переименовывается в исходное имя. (Есть, конечно, различные меры предосторожности для предотвращения dataloss.) Это, например, стиль, используемый sed или perl при вызове с помощью -i ("in-place "), который на самом деле не« на месте ». Его следовало бы назвать «новым местом со старым именем».

Это хорошо работает, потому что unix уверяет (по крайней мере, для локальных файловых систем), что открытый файл продолжает существовать до его закрытия, даже если он «удален», и создается новый файл с тем же именем. (Не случайно, что системный вызов unix для «удаления» файла на самом деле называется «unlink».) Итак, вообще говоря, если интерпретатор оболочки имеет некоторый исходный файл, и вы «редактируете» файл описанным выше способом , оболочка даже не увидит изменения, так как все еще имеет открытый файл.

[Примечание: как и во всех комментариях, основанных на стандартах, вышеперечисленное подвержено множественным интерпретациям и существуют различные угловые регистры, такие как NFS. Педанты могут заполнить комментарии исключениями.]

Конечно, можно напрямую изменять файлы; это просто не очень удобно для редактирования, потому что, хотя вы можете перезаписывать данные в файле, вы не можете удалить или вставить, не перекладывая все следующие данные, что будет означать довольно много переписывания. Кроме того, в то время как вы делали это изменение, содержимое файла было бы непредсказуемым, и процессы, у которых был открытый файл, пострадали. Чтобы уйти от этого (как, например, с системами баз данных), вам нужен сложный набор протоколов модификации и распределенных блокировок; материал, который выходит за рамки обычной утилиты для редактирования файлов.

Итак, если вы хотите редактировать файл во время его обработки оболочкой, у вас есть две возможности:

  1. Вы можете добавить файл. Это всегда должно работать.

  2. Вы можете перезаписать файл с новым содержимым точно такой же длины . Это может работать или не работать, в зависимости от того, прочитала ли оболочка эту часть файла или нет. Поскольку большинство операций ввода-вывода файлов связаны с буферами чтения, и поскольку все мои оболочки, которые я знаю, читают всю составную команду перед ее исполнением, маловероятно, что вы можете избежать этого. Это, конечно, не было бы надежным.

Я не знаю каких-либо формулировок в стандарте Posix, который фактически требует возможности добавления файла сценария во время выполнения файла, поэтому он может не работать с каждой совместимой с 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}   #
ответил rici 28 PM00000090000003431 2013, 21:08:34
15

bash долгий путь, чтобы убедиться, что он читает команды непосредственно перед их выполнением.

Например, в:

cmd1
cmd2

Оболочка будет читать скрипт по блокам, поэтому, вероятно, прочитайте обе команды, интерпретируйте первый, а затем вернитесь к концу cmd1 в скрипте и снова прочитать скрипт, чтобы прочитать cmd2 и выполнить его.

Вы можете легко проверить это:

$ cat a
echo foo | dd 2> /dev/null bs=1 seek=50 of=a
echo bar
$ bash a
foo

(хотя, глядя на вывод strace на нем, кажется, он делает несколько более причудливых вещей (например, прочитал данные несколько раз, искать назад ...), чем когда я пробовал то же самое несколько лет назад, поэтому мое заявление выше о lseeking назад может больше не применяться в более новых версиях).

Если вы пишете свой сценарий как:

{
  cmd1
  cmd2
  exit
}

Оболочке придется прочитать до закрывающего }, сохранить это в памяти и выполнить. Из-за exit, оболочка больше не будет считывать из сценария, чтобы вы могли безопасно редактировать его, когда оболочка интерпретирует его.

Кроме того, при редактировании сценария убедитесь, что вы пишете новую копию скрипта. Оболочка будет продолжать читать исходную (даже если она удалена или переименована).

Для этого переименуйте the-script в the-script.old и скопировать the-script.old в the-script и отредактируйте его.

ответил Stéphane Chazelas 28 AM000000100000002531 2013, 10:06:25
4

На самом деле нет безопасного способа изменить скрипт во время его запуска, потому что оболочка может использовать буферизацию для чтения файла. Кроме того, если скрипт изменен, заменив его новым файлом, оболочки обычно будут читать только новый файл после выполнения определенных операций.

Часто, когда скрипт изменяется во время выполнения, оболочка заканчивает сообщение о синтаксических ошибках. Это связано с тем, что, когда оболочка закрывает и снова открывает файл сценария, он использует смещение байта в файл, чтобы поместить его обратно.

ответил ash 28 PM00000050000000731 2013, 17:31:07
4

Вы можете обойти это, установив ловушку на свой скрипт, а затем используя exec, чтобы получить новое содержимое скрипта. Обратите внимание, однако, вызов exec запускает скрипт с нуля, а не с того места, где он достигнут в запущенном процессе, и поэтому скрипт B получит (далее).

#! /bin/bash

CMD="$0"
ARGS=("[email protected]")

trap reexec 1

reexec() {
    exec "$CMD" "${ARGS[@]}"
}

while : ; do sleep 1 ; clear ; date ; done

Это будет продолжать показывать дату на экране. Затем я мог бы изменить свой сценарий и change date до echo "Date: $(date)". При написании этого сценария показывается только дата. Как бы то ни было, если я отправлю сигнал, чтобы установить trap, сценарий будет exec (заменяет текущий текущий процесс указанной командой), которая является командой $CMD и arguments [email protected]. Вы можете сделать это, выпустив kill -1 PID - где PID - это PID исполняемого сценария - и выход изменится, чтобы показать Date: перед выходом команды date.

Вы можете сохранить «состояние» вашего скрипта во внешнем файле (в say /tmp) и прочитать содержимое, чтобы знать, где «возобновить», когда программа повторно выполняется. Затем вы можете добавить дополнительное прерывание ловушек (SIGINT /SIGQUIT /SIGKILL /SIGTERM), чтобы очистить этот файл tmp, поэтому при перезапуске после прерывания «Script A» он начнется с самого начала. Версия с состоянием будет выглядеть примерно так:

#! /bin/bash

trap reexec 1
trap cleanup 2 3 9 15

CMD="$0"
ARGS=("[email protected]")
statefile='/tmp/scriptA.state'
EXIT=1

reexec() { echo "Restarting..." ; exec "$CMD" "${ARGS[@]}"; }
cleanup() { rm -f $statefile; exit $EXIT; }
run_scriptB() { /path/to/scriptB; echo "scriptC" > $statefile; }
run_scriptC() { /path/to/scriptC; echo "stop" > $statefile;  }

while [ "$state" != "stop" ] ; do

    if [ -f "$statefile" ] ; then
        state="$(cat "$statefile")"
    else
        state='starting'
    fi

    case "$state" in
        starting)         
            run_scriptB
        ;;
        scriptC)
            run_scriptC
        ;;
    esac
done

EXIT=0
cleanup
ответил Drav Sloan 28 AM00000080000002831 2013, 08:47:28

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132