Trap, ERR и отображение строки ошибки

30

Я пытаюсь создать несколько отчетов об ошибках, используя Trap для вызова функции по всем ошибкам:

Trap "_func" ERR

Можно ли узнать, с какой линии был отправлен сигнал ERR? Оболочка Баш.

Если я это сделаю, я смогу прочитать и сообщить, какая команда использовалась, и записать / выполнить некоторые действия.

Или, может быть, я все это неправильно?

Я проверил со следующим:

#!/bin/bash
trap "ECHO $LINENO" ERR

echo hello | grep "asdf"

И $LINENOвозвращается 2. не работает.

Mechaflash
источник
Вы можете посмотреть на скрипт отладчика bash bashdb. Кажется, что первый аргумент trapможет содержать переменные, которые оцениваются в нужном контексте. Так trap 'echo $LINENO' ERR'должно работать.
удачно сделано
хм только что попробовал это с плохим эхом | grep и возвращает строку оператора Trap. Но я посмотрю на bashdb
Mechaflash
Мне очень жаль ... Я не указал в своем первоначальном вопросе, что мне нужно собственное решение. Я редактировал вопрос.
Mechaflash
К сожалению, я BORKED пример строки: trap 'echo $LINENO' ERR. Первым аргументом trapявляется вся echo $LINENOжесткая цитата. Это в Баш.
удачно сделано
5
@Mechaflash Должно быть trap 'echo $LINENO' ERR, с одинарными кавычками, а не с двойными. С помощью команды, которую вы написали, $LINENOраскрывается при разборе строки 2, поэтому ловушка echo 2(точнее ECHO 2, будет выводиться bash: ECHO: command not found).
Жиль "ТАК - перестань быть злым"

Ответы:

61

Как указано в комментариях, ваша цитата неверна. Вам нужны одинарные кавычки, чтобы предотвратить $LINENOрасширение при первом анализе строки ловушки.

Это работает:

#! /bin/bash

err_report() {
    echo "Error on line $1"
}

trap 'err_report $LINENO' ERR

echo hello | grep foo  # This is line number 9

Запуск это:

 $ ./test.sh
 Error on line 9
Mat
источник
спасибо за пример с вызовом функции. Я не знал, что двойные кавычки расширили переменную в этом случае.
Mechaflash
echo hello | grep fooкажется, не выдает ошибку для меня. Я что-то неправильно понимаю?
геотеория
@geotheory В моей системе grepсостояние выхода равно 0, если совпадение было, 1, если совпадения не было, и> 1 для ошибки. Вы можете проверить поведение в вашей системе сecho hello | grep foo; echo $?
Патрик
Нет, вы правы, это ошибка :)
geotheory
Вам не нужно использовать -e в строке вызова, чтобы вызвать ошибку при сбое команды? То есть: #! / Bin / bash -e?
Тим Берд
14

Вы также можете использовать встроенную в Caller команду:

#!/bin/bash

err_report() {
  echo "errexit on line $(caller)" >&2
}

trap err_report ERR

echo hello | grep foo

он также печатает имя файла:

$ ./test.sh
errexit on line 9 ./test.sh
Андрей Иванов
источник
7

Мне действительно нравится ответ, данный @Mat выше. Основываясь на этом, я написал небольшой помощник, который дает немного больше контекста для ошибки:

Мы можем проверить скрипт на наличие строки, которая вызвала сбой:

err() {
    echo "Error occurred:"
    awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0
}
trap 'err $LINENO' ERR

Вот это в небольшом тестовом скрипте:

#!/bin/bash

set -e

err() {
    echo "Error occurred:"
    awk 'NR>L-4 && NR<L+4 { printf "%-5d%3s%s\n",NR,(NR==L?">>>":""),$0 }' L=$1 $0
}
trap 'err $LINENO' ERR

echo one
echo two
echo three
echo four
false
echo five
echo six
echo seven
echo eight

Когда мы запускаем его, мы получаем:

$ /tmp/test.sh
one
two
three
four
Error occurred:
12      echo two
13      echo three
14      echo four
15   >>>false
16      echo five
17      echo six
18      echo seven
unpythonic
источник
Было бы еще лучше использовать $(caller)данные для определения контекста, даже если ошибка не в текущем скрипте, а в одном из его импортов. Очень хорошо, хотя!
Триссе
2

Вдохновленный другим ответом, вот более простой контекстный обработчик ошибок:

trap '>&2 echo Command failed: $(tail -n+$LINENO $0 | head -n1)' ERR

Вы также можете использовать awk вместо tail & head, если это необходимо.

sanmai
источник
1
есть причина, по которой другой ответ предоставляет контекст в виде 3 строк выше и 3 строк ниже ошибочной строки - что, если ошибка исходит из строки продолжения?
Iruvar
@iruvar это понятно, но мне не нужен какой-то дополнительный контекст; одна строка контекста настолько проста, насколько это возможно, и настолько, насколько мне нужно
sanmai
Хорошо, мой друг, +1
Ирувар
1

Вот еще одна версия, вдохновленная @sanmai и @unpythonic. Он показывает строки сценария вокруг ошибки, с номерами строк и статусом выхода - используя tail & head, так как это кажется проще, чем решение awk.

Показывая это в виде двух строк здесь для удобства чтения - вы можете объединить эти строки в одну, если хотите (сохраняя ;):

trap 'echo >&2 "Error - exited with status $? at line $LINENO:"; 
         pr -tn $0 | tail -n+$((LINENO - 3)) | head -n7' ERR

Это работает очень хорошо с set -euo pipefail( неофициальный строгий режим ) - любая неопределенная ошибка переменной дает номер строки без запуска ERRпсевдосигнала, но другие случаи действительно показывают контекст.

Пример вывода:

myscript.sh: line 27: blah: command not found
Error - exited with status 127 at line 27:
   24   # Do something
   25   lines=$(wc -l /etc/passwd)
   26   # More stuff
   27   blah
   28   
   29   # Check time
   30   time=$(date)
RichVel
источник
0

Можно ли узнать, с какой линии был отправлен сигнал ERR?

Да, LINENOи BASH_LINENOпеременные supper полезны для получения линии сбоя и линий, которые приводят к нему.

Или, может быть, я все это неправильно?

Нет, просто отсутствует -qопция с grep ...

echo hello | grep -q "asdf"

... С -qопцией grepвернемся 0за trueи 1за false. И в Баш это trapне Trap...

trap "_func" ERR

... мне нужно нативное решение ...

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

failure.sh

## Outputs Front-Mater formatted failures for functions not returning 0
## Use the following line after sourcing this file to set failure trap
##    trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
failure(){
    local -n _lineno="${1:-LINENO}"
    local -n _bash_lineno="${2:-BASH_LINENO}"
    local _last_command="${3:-${BASH_COMMAND}}"
    local _code="${4:-0}"

    ## Workaround for read EOF combo tripping traps
    if ! ((_code)); then
        return "${_code}"
    fi

    local _last_command_height="$(wc -l <<<"${_last_command}")"

    local -a _output_array=()
    _output_array+=(
        '---'
        "lines_history: [${_lineno} ${_bash_lineno[*]}]"
        "function_trace: [${FUNCNAME[*]}]"
        "exit_code: ${_code}"
    )

    if [[ "${#BASH_SOURCE[@]}" -gt '1' ]]; then
        _output_array+=('source_trace:')
        for _item in "${BASH_SOURCE[@]}"; do
            _output_array+=("  - ${_item}")
        done
    else
        _output_array+=("source_trace: [${BASH_SOURCE[*]}]")
    fi

    if [[ "${_last_command_height}" -gt '1' ]]; then
        _output_array+=(
            'last_command: ->'
            "${_last_command}"
        )
    else
        _output_array+=("last_command: ${_last_command}")
    fi

    _output_array+=('---')
    printf '%s\n' "${_output_array[@]}" >&2
    exit ${_code}
}

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

example_usage.sh

#!/usr/bin/env bash

set -E -o functrace

## Optional, but recommended to find true directory this script resides in
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
    __SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's@^.* -> \(.*\)@\1@p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"


## Source module code within this script
source "${__DIR__}/modules/trap-failure/failure.sh"

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR


something_functional() {
    _req_arg_one="${1:?something_functional needs two arguments, missing the first already}"
    _opt_arg_one="${2:-SPAM}"
    _opt_arg_two="${3:0}"
    printf 'something_functional: %s %s %s' "${_req_arg_one}" "${_opt_arg_one}" "${_opt_arg_two}"
    ## Generate an error by calling nothing
    "${__DIR__}/nothing.sh"
}


## Ignoring errors prevents trap from being triggered
something_functional || echo "Ignored something_functional returning $?"
if [[ "$(something_functional 'Spam!?')" == '0' ]]; then
    printf 'Nothing somehow was something?!\n' >&2 && exit 1
fi


## And generating an error state will cause the trap to _trace_ it
something_functional '' 'spam' 'Jam'

Вышесказанное проверено на Bash версии 4+, поэтому оставьте комментарий, если требуется что-то для версий до четырех, или откройте проблему, если она не может перехватить сбои в системах с минимальной версией четыре.

Основными блюдами являются ...

set -E -o functrace
  • -Eвызывает ошибки в функции к пузыриться

  • -o functrace причин позволяет больше многословия, когда что-то внутри функции не удается

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
  • Одиночные кавычки используются вокруг вызова функции, а двойные кавычки - вокруг отдельных аргументов.

  • Ссылки на LINENOи BASH_LINENOпередаются вместо текущих значений, хотя это может быть сокращено в более поздних версиях, связанных с ловушкой, так что последняя строка ошибки превращает ее в вывод

  • Значения BASH_COMMANDи exit status ( $?) передаются, во-первых, чтобы получить команду, которая возвратила ошибку, и во-вторых, чтобы убедиться, что ловушка не срабатывает при статусах без ошибок.

И хотя другие могут не согласиться, я считаю, что проще создать выходной массив и использовать printf для печати каждого элемента массива в отдельной строке ...

printf '%s\n' "${_output_array[@]}" >&2

... также >&2бит в конце приводит к тому, что ошибки идут туда, куда должны (стандартная ошибка), и позволяет фиксировать только ошибки ...

## ... to a file...
some_trapped_script.sh 2>some_trapped_errros.log

## ... or by ignoring standard out...
some_trapped_script.sh 1>/dev/null

Как показано в этих и других примерах переполнения стека, существует множество способов создания средства отладки с использованием встроенных утилит.

S0AndS0
источник