Bẫy, ERR và lặp lại dòng lỗi


30

Tôi đang cố gắng tạo một số báo cáo lỗi bằng cách sử dụng Bẫy để gọi hàm trên tất cả các lỗi:

Trap "_func" ERR

Có thể lấy dòng tín hiệu ERR được gửi từ đâu không? Vỏ là bash.

Nếu tôi làm điều đó, tôi có thể đọc và báo cáo lệnh nào đã được sử dụng và đăng nhập / thực hiện một số hành động.

Hoặc có lẽ tôi đang làm điều này sai?

Tôi đã thử nghiệm như sau:

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

echo hello | grep "asdf"

$LINENOđang trở lại 2. Không hoạt động.


Bạn có thể nhìn vào tập lệnh gỡ lỗi bash bashdb. Có vẻ như đối số đầu tiên trapcó thể chứa các biến được đánh giá trong ngữ cảnh mong muốn. Vì vậy trap 'echo $LINENO' ERR'nên làm việc.
donothings thành công

hmm chỉ thử điều này với một tiếng vang xấu | lệnh grep và nó trả về dòng của câu lệnh Bẫy. Nhưng tôi sẽ xem bashdb
Mechaflash

Tôi rất xin lỗi ... Tôi đã không xác định trong câu hỏi ban đầu của mình rằng tôi cần một giải pháp bản địa. Tôi chỉnh sửa câu hỏi.
Mechaflash

Xin lỗi, tôi đã borked dòng ví dụ : trap 'echo $LINENO' ERR. Đối số đầu tiên traplà toàn bộ phần echo $LINENOcứng. Đây là trong bash.
donothings thành công

5
@Mechaflash Nó sẽ phải trap 'echo $LINENO' ERR, với dấu ngoặc đơn, không phải dấu ngoặc kép. Với lệnh bạn đã viết, $LINENOđược mở rộng khi dòng 2 được phân tích cú pháp, do đó, bẫy là echo 2(hoặc đúng hơn ECHO 2, sẽ xuất ra bash: ECHO: command not found).
Gilles 'SO- ngừng trở nên xấu xa'

Câu trả lời:


61

Như đã chỉ ra trong các ý kiến, trích dẫn của bạn là sai. Bạn cần các dấu ngoặc đơn để tránh $LINENObị mở rộng khi dòng bẫy được phân tích cú pháp đầu tiên.

Những công việc này:

#! /bin/bash

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

trap 'err_report $LINENO' ERR

echo hello | grep foo  # This is line number 9

Chạy nó:

 $ ./test.sh
 Error on line 9

cảm ơn ví dụ với một cuộc gọi chức năng. Tôi không biết rằng dấu ngoặc kép đã mở rộng biến trong trường hợp này.
Mechaflash

echo hello | grep foodường như không ném lỗi cho tôi. Tôi có hiểu lầm gì không?
geotheory

@geotheory Trên hệ thống của tôi grepcó trạng thái thoát là 0 nếu có trận đấu, 1 nếu không có kết quả khớp và> 1 cho một lỗi. Bạn có thể kiểm tra hành vi trên hệ thống của mình vớiecho hello | grep foo; echo $?
Patrick

Không đúng, đó là một lỗi :)
geotheory

Bạn không cần phải sử dụng -e trên dòng gọi, để gây ra lỗi khi lỗi lệnh? Đó là: #! / Bin / bash -e?
Tim Bird

14

Bạn cũng có thể sử dụng 'người gọi' bash dựng sẵn:

#!/bin/bash

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

trap err_report ERR

echo hello | grep foo

Nó cũng in tên tệp:

$ ./test.sh
errexit on line 9 ./test.sh

6

Tôi thực sự thích câu trả lời được đưa ra bởi @Mat ở trên. Dựa trên điều này, tôi đã viết một trình trợ giúp nhỏ cung cấp thêm một chút bối cảnh cho lỗi:

Chúng tôi có thể kiểm tra tập lệnh cho dòng gây ra lỗi:

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

Đây là một kịch bản thử nghiệm nhỏ:

#!/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

Khi chúng tôi chạy nó, chúng tôi nhận được:

$ /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

Điều này thậm chí còn tốt hơn khi sử dụng $(caller)dữ liệu của để cung cấp ngữ cảnh ngay cả khi lỗi không nằm trong tập lệnh hiện tại mà là một trong những lần nhập của nó. Mặc dù rất đẹp!
tricasse

2

Lấy cảm hứng từ câu trả lời khác, đây là một trình xử lý lỗi theo ngữ cảnh đơn giản hơn:

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

Bạn cũng có thể sử dụng awk thay vì đuôi & đầu nếu cần.


1
có một lý do câu trả lời khác cung cấp ngữ cảnh bằng 3 dòng trên và 3 dòng bên dưới dòng vi phạm - điều gì xảy ra nếu lỗi xuất phát từ một dòng tiếp tục?
iruvar

@iruvar điều này được hiểu, nhưng tôi không cần bất kỳ bối cảnh bổ sung nào; một dòng bối cảnh đơn giản như nó có, và đủ như tôi cần
sanmai

Được rồi bạn của tôi, + 1
iruvar

1

Đây là một phiên bản khác, lấy cảm hứng từ @sanmai và @unpythonic. Nó hiển thị các dòng script xung quanh lỗi, với số dòng và trạng thái thoát - sử dụng đuôi & đầu vì điều đó có vẻ đơn giản hơn so với giải pháp awk.

Hiển thị đây là hai dòng ở đây để dễ đọc - bạn có thể nối các dòng này thành một nếu bạn thích (giữ nguyên ;):

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

Điều này hoạt động khá tốt với set -euo pipefail( chế độ nghiêm ngặt không chính thức ) - bất kỳ lỗi biến không xác định nào cũng cho số dòng mà không kích hoạt ERRtín hiệu giả, nhưng các trường hợp khác không hiển thị ngữ cảnh.

Ví dụ đầu ra:

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)

0

Có thể lấy dòng tín hiệu ERR được gửi từ đâu không?

Có, LINENOBASH_LINENOcác biến là siêu hữu ích để có được dòng thất bại và các dòng dẫn đến nó.

Hoặc có lẽ tôi đang làm điều này sai?

Không, chỉ thiếu -qtùy chọn với grep ...

echo hello | grep -q "asdf"

... Với sự -qlựa chọn grepsẽ trở lại 0cho true1cho false. Và ở Bash, nó trapkhông Trap...

trap "_func" ERR

... Tôi cần một giải pháp bản địa ...

Đây là một cái bẫy mà bạn có thể thấy hữu ích để gỡ lỗi những thứ có độ phức tạp chu kỳ hơn một chút ...

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}
}

... và một kịch bản sử dụng ví dụ để phơi bày sự khác biệt tinh tế trong cách đặt bẫy ở trên để theo dõi chức năng ...

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'

Ở trên đã thử nghiệm trên Bash phiên bản 4+, vì vậy hãy để lại nhận xét nếu cần một cái gì đó cho các phiên bản trước bốn hoặc Mở một vấn đề nếu nó không bẫy được các lỗi trên các hệ thống có phiên bản tối thiểu là bốn.

Các chính takeaways là ...

set -E -o functrace
  • -Egây ra lỗi trong các chức năng để nổi bong bóng

  • -o functrace nguyên nhân cho phép nhiều chi tiết hơn khi một cái gì đó trong một chức năng không thành công

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
  • Dấu ngoặc đơn được sử dụng xung quanh lệnh gọi hàm và dấu ngoặc kép nằm xung quanh các đối số riêng lẻ

  • Các tham chiếu đến LINENOBASH_LINENOđược truyền thay cho các giá trị hiện tại, mặc dù điều này có thể được rút ngắn trong các phiên bản sau của liên kết với bẫy, do đó, dòng thất bại cuối cùng biến nó thành đầu ra

  • Các giá trị của BASH_COMMANDtrạng thái thoát và thoát ( $?) được thông qua, đầu tiên để nhận lệnh trả về lỗi và thứ hai để đảm bảo rằng bẫy không kích hoạt các trạng thái không lỗi

Và trong khi những người khác có thể không đồng ý, tôi thấy việc xây dựng một mảng đầu ra dễ dàng hơn và sử dụng printf để in từng phần tử mảng trên dòng riêng của nó ...

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

... cũng là >&2bit ở cuối gây ra lỗi ở đâu (lỗi tiêu chuẩn) và cho phép chỉ bắt lỗi ...

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

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

Như được thể hiện bởi những điều này và các ví dụ khác trên Stack Overflow, có rất nhiều cách để xây dựng một công cụ gỡ lỗi bằng cách sử dụng các tiện ích tích hợp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.