Intereting Posts
«Показать localhost: 0 недоступен» и «xhost: невозможно открыть отображение« localhost: 0 »в локальном терминале (не SSH) на Fedora 25 Каталоги «ls -lh» сообщили о размере: Linux-диски, совместимые с CIFS Специальная установка CentOS 7: транзакция с ошибкой отправить супер ключ на удаленную сессию Citrix Изменение языка системы с итальянского на английский в Debian 9 DE без изменения настроек клавиатуры Проблема с bash-скриптом при запуске с панели запуска Переопределить черный список modprobe.d сравнение значений десятичной точки в выражении if Как отслеживать жизненный цикл потоков ядра, следуя созданию и переключению? Удалить параметры выключения / перезагрузки из окна выхода Как устранить ошибку ядра Fedora Создайте новый Hotspot с помощью только Terminal и NetworkManager / nmcli Каков наилучший способ синхронизации файлов с разделом VFAT? Как развернуть Firefox Bookmarks для всех пользователей в сети? Учебный материал Sed and Awk

Неправильный $ LINENO для захваченной функции

Я пишу сценарий Bash для себя, чтобы изучить скрипты. В какой-то момент мне нужно добавить ловушку, чтобы очистить нежелательные каталоги и файлы, если скрипт убит. Однако по какой-то причине я не понимаю, ловушка вызывает функцию очистки – clean_a() – когда скрипт убит, но $LINENO указывает на строку в самой функции очистки, а не на функцию – archieve_it() – когда скрипт убит.

Ожидаемое поведение:

  1. сценарий запуска
  2. Нажмите Ctrl + C
  3. trap caches Ctrl + C и вызывает clean_a()
  4. clean_a() номер строки, на которую нажимается Ctrl + C. Пусть это будет строка 10 в archieve_it() .

Что на самом деле происходит:

  1. сценарий запуска
  2. Нажмите Ctrl + C
  3. trap caches Ctrl + C и вызывает clean_a()
  4. clean_a() перекликается с нерелевантным номером строки. Скажем, строка 25 в функции clean_a() .

Вот пример как часть моего скрипта:

 archieve_it () { trap 'clean_a $LINENO $BASH_COMMAND'\ SIGHUP SIGINT SIGTERM SIGQUIT for src in ${sources}; do mkdir -p "${dest}${today}${src}" if [[ "$?" -ne 0 ]] ; then error "Something!" fi rsync "${options}" \ --stats -i \ --log-file="${dest}${rsync_log}" \ "${excludes}" "${src}" "${dest}${today}${src}" done } clean_a () { error "something! line: $LINENO command: $BASH_COMMAND removing ${dest}${today}..." cd "${dest}" rm -rdf "${today}" exit "$1" } 

PS: Оригинальный сценарий можно увидеть здесь . Определения и имена переменных находятся на турецком языке. Если это необходимо, я могу перевести что-нибудь на английский.

EDIT: Я меняю сценарий так, как могу, в соответствии с объяснением @ mikeserv:

 #!/bin/bash PS4='DEBUG: $((LASTNO=$LINENO)) : '; set -x archieve_it () { trap 'clean_a $LASTNO $LINENO $BASH_COMMAND'\ SIGHUP SIGINT SIGTERM SIGQUIT .. } clean_a () { error " ... line: $LINENO $LASTNO ..." } 

Теперь, если я запускаю скрипт с set -x и завершаю его с помощью Ctrl + C , он печатает правильный номер строки, как показано ниже:

  DDEBUG: 1 : clean_a 1 336 rsync '"${options}"' ... 

Однако в функции clean_a() значение $LASTNO печатается как 1.

  line: 462 1 

Это имеет какое-то отношение к ошибке, которую показывает @Arkadiusz Drabczyk?

EDIT2 : Я сменил сценарий так же, как мне рекомендовал @mikesrv. Но $ LASTNO возвратил 1 как значение строки, когда скрипт был прерван (это должно было быть 337).

 #!/bin/bash PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x archieve_it () { trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \ SIGHUP SIGINT SIGTERM SIGQUIT ... } 2>/dev/null clean_a () { error " ... line: $LASTNO $LINENO ..." } 2>&1 

Если я запускаю скрипт и завершаю его с помощью Ctrl + C во время работы rsync, я получаю этот вывод:

 ^^MDEBUG: 1 : clean_a '337 1 rsync "${options}" --delete-during ... ... line: 1 465 

Как вы можете видеть, значение $ LASTNO равно 1.

Пока я пытался выяснить, в чем проблема, я написал еще одну функцию – testing – используя формат подстановки параметра ${parameter:-default} . Итак, скрипт получился так:

 #!/bin/bash PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x archieve_it () { trap 'testing "$LASTNO $LINENO $BASH_COMMAND"'\ SIGHUP SIGINT SIGTERM SIGQUIT ... } 2>/dev/null testing() { echo -e "${1:-Unknown error!}" exit 1 } 2>&1 

Теперь, если я запускаю скрипт и нажимаю Ctrl + C , я получаю этот вывод:

 ^^MDEBUG: 1 : testing '337 1 rsync "${options}" --delete-during ... 337 1 rsync "${options}" --delete-during ... 

337 указывает линию, когда я нажимал Ctrl + C , а rsync запускался.

Для другого теста я попытался написать clear_a funtion следующим образом:

 clear_a () { echo -e " $LASTNO $LINENO" } 

и $ LASTNO все еще вернулись 1.

Итак, это означает, что мы можем получить правильный номер строки, когда скрипт завершен, если мы будем использовать замену параметров?

EDIT3 Кажется, я неправильно применил объяснение @ mikeserv в EDIT2. Я исправил свою ошибку. Позиционный параметр "$1 должен быть заменен $ LASTNO в clear_a funtion.

Вот сценарий, который работает так, как я хочу, чтобы он работал:

 #!/bin/bash PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x archieve_it () { trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \ SIGHUP SIGINT SIGTERM SIGQUIT ... } 2>/dev/null clean_a () { error " ... line: $1 ..." } 2>&1 

Когда скрипт завершается, trap оценивает $LASTNO – первый аргумент -, $LINENO – второй аргумент – и $BASH_COMMAND$BASH_COMMAND аргумент -, затем передает свои значения функции clear_a . Наконец, мы печатаем $ LASTNO с $1 как номер строки, сценарий которой завершен.

Я думаю, проблема в том, что вы ожидаете, что "$LINENO" даст вам линию исполнения для последней команды, которая может почти сработать, но clean_a() также получает свой собственный $LINENO и что вы должны сделать вместо этого:

 error "something! line: $1 ... 

Но даже это, вероятно, не сработает, потому что я ожидаю, что он просто напечатает строку, на которой вы установите trap .

Вот небольшая демонстрация:

 PS4='DEBUG: $LINENO : ' \ bash -x <<\CMD trap 'fn "$LINENO"' EXIT fn() { printf %s\\n "$LINENO" "$1"; } echo "$LINENO" CMD 

ВЫВОД

 DEBUG: 1 : trap 'fn "$LINENO"' EXIT DEBUG: 3 : echo 3 3 DEBUG: 1 : fn 1 DEBUG: 2 : printf '%s\n' 2 1 2 1 

Таким образом, trap устанавливается, тогда определяется fn() , затем выполняется echo . Когда оболочка завершает выполнение своего ввода, ловушка EXIT запускается и вызывается fn . Ему передается один аргумент, который является линией trap $LINENO . fn сначала печатает свой собственный $LINENO затем его первый аргумент.

Я могу думать о том, как вы можете получить такое поведение, которое вы ожидаете, но оно вроде бы подталкивает stderr оболочки:

 PS4='DEBUG: $((LASTNO=$LINENO)) : ' \ bash -x <<\CMD trap 'fn "$LINENO" "$LASTNO"' EXIT fn() { printf %s\\n "$LINENO" "$LASTNO" "$@"; } echo "$LINENO" CMD 

ВЫВОД

 DEBUG: 1 : trap 'fn "$LINENO" "$LASTNO"' EXIT DEBUG: 3 : echo 3 3 DEBUG: 1 : fn 1 3 DEBUG: 2 : printf '%s\n' 2 1 1 3 2 1 1 3 

Он использует приглашение отладки $PS4 оболочки для определения $LASTNO в каждой выполненной строке. Это текущая переменная оболочки, доступ к которой вы можете получить в любом месте скрипта. Это означает, что независимо от того, какая строка в настоящее время доступна, вы можете ссылаться на самую последнюю строку сценария, запущенную в $LASTNO . Конечно, как вы можете видеть, он поставляется с отладочной выводом. Вы можете нажать это на 2>/dev/null для большей части исполнения скрипта, а затем просто 2>&1 в clean_a() или что-то еще.

Причина, по которой вы получаете 1 в $LASTNO заключается в том, что это последнее значение, для которого был установлен $LASTNO потому что это было последнее значение $LINENO . У вас есть ваша trap в функции archieve_it() и поэтому она получает свой собственный $LINENO как указано в спецификации ниже. Хотя это и не кажется, что bash делает все правильно, так что это может быть связано также с тем, что trap должна повторно выполнить оболочку на сигнале INT и поэтому перезагружается $LINENO . В этом случае я немного расплывчатый – как и bash , по-видимому.

Думаю, вы не хотите оценивать $LASTNO в clean_a() . Лучше было бы оценить его в trap и передать trap значения в $LASTNO до clean_a() в качестве аргумента. Может быть, вот так:

 #!/bin/bash PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x archieve_it () { trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \ SIGHUP SIGINT SIGTERM SIGQUIT while :; do sleep 1; done } 2>/dev/null clean_a () { : "$@" ; } 2>&1 

Попытайтесь – я думаю, он должен делать то, что вы хотите. Ох – и обратите внимание, что в PS4=^M ^M является буквальным возвратом – например CTRL + V ENTER.

Из спецификации оболочки POSIX :

Установите оболочкой десятичное число, представляющее текущий номер последовательной строки (пронумерованный начиная с 1) внутри скрипта или функции, прежде чем он выполнит каждую команду. Если пользователь отключает или сбрасывает LINENO , переменная может потерять свое особое значение для жизни оболочки. Если оболочка в настоящий момент не выполняет скрипт или функцию, значение LINENO не LINENO . Этот объем IEEE Std 1003.1-2001 определяет влияние переменной только для систем, поддерживающих опцию User Portability Utilities.

Решение mikeserv – это хорошо, но он неверно говорит, что fn передается trap $LINENO когда ловушка выполняется. Вставьте строку перед trap ... и вы увидите, что fn на самом деле всегда передается 1 , независимо от того, где была объявлена ​​ловушка.

 PS4='DEBUG: $LINENO : ' \ bash -x <<\EOF echo Foo trap 'fn "$LINENO"' EXIT fn() { printf %s\\n "$LINENO" "$1"; } echo "$LINENO" exit EOF 

ВЫВОД

 DEBUG: 1 : echo Foo Foo DEBUG: 2 : trap 'fn "$LINENO"' EXIT DEBUG: 4 : echo 4 4 DEBUG: 5 : exit DEBUG: 1 : fn 1 DEBUG: 3 : printf '%s\n' 3 1 3 1 

Поскольку первый аргумент для ловушки, fn "$LINENO" , помещается в одинарные кавычки, $LINENO расширяется , тогда и только тогда, когда EXIT запускается и поэтому должен расширяться до fn 5 . Так почему же это не так? Фактически, это произошло до bash-4.0, когда он был намеренно изменен, так что $ LINENO сбрасывается до 1 при срабатывании ловушки и, следовательно, расширяется до fn 1 . [source] Исходное поведение по-прежнему поддерживается для ловушек ERR, однако, вероятно, потому, что часто используется нечто вроде trap 'echo "Error at line $LINENO"' ERR .

 #!/bin/bash trap 'echo "exit at line $LINENO"' EXIT trap 'echo "error at line $LINENO"' ERR false exit 0 

ВЫВОД

 error at line 5 exit at line 1 

тестируется с GNU bash, версия 4.3.42 (1) -release (x86_64-pc-linux-gnu)