Trap, ERR และ echoing บรรทัดข้อผิดพลาด


30

ฉันกำลังพยายามสร้างการรายงานข้อผิดพลาดโดยใช้ Trap เพื่อเรียกใช้ฟังก์ชันกับข้อผิดพลาดทั้งหมด:

Trap "_func" ERR

เป็นไปได้ไหมที่จะรับสัญญาณ ERR ที่เป็นเส้นตรง? เปลือกเป็นทุบตี

หากฉันทำเช่นนั้นฉันสามารถอ่านและรายงานคำสั่งที่ใช้และบันทึก / ดำเนินการบางอย่าง

หรือบางทีฉันอาจจะทำผิดทั้งหมดนี้?

ฉันทดสอบกับสิ่งต่อไปนี้:

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

echo hello | grep "asdf"

และ$LINENOกำลังกลับมา 2 ไม่ทำงาน


bashdbคุณสามารถดูสคริปต์ทุบตีดีบัก ดูเหมือนว่าอาร์กิวเมนต์แรกที่trapสามารถมีตัวแปรที่ประเมินในบริบทที่ต้องการ ดังนั้นtrap 'echo $LINENO' ERR'ควรทำงาน
donothingsuccessfully

อืมพยายามทำสิ่งนี้ด้วยเสียงสะท้อนที่ไม่ดี คำสั่ง grep และส่งคืนบรรทัดคำสั่ง Trap แต่ฉันจะดู bashdb
Mechaflash

ฉันขอโทษด้วย ... ฉันไม่ได้ระบุในคำถามดั้งเดิมที่ฉันต้องการวิธีแก้ปัญหาดั้งเดิม ฉันแก้ไขคำถาม
Mechaflash

ขอโทษนะฉัน borked trap 'echo $LINENO' ERRบรรทัดตัวอย่าง: อาร์กิวเมนต์แรกtrapคือecho $LINENOhardquote ทั้งหมด นี่คือทุบตี
donothingsuccessfully

5
@Mechaflash มันจะต้องเป็นtrap 'echo $LINENO' ERRด้วยคำพูดเดียวไม่พูดสองครั้ง ด้วยคำสั่งที่คุณเขียน$LINENOจะถูกขยายเมื่อบรรทัดที่ 2 ถูกแยกวิเคราะห์ดังนั้นกับดักecho 2(หรือมากกว่าECHO 2ซึ่งจะส่งออกbash: ECHO: command not found)
Gilles 'หยุดความชั่วร้าย' ใน

คำตอบ:


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

ขอบคุณสำหรับตัวอย่างด้วยการเรียกใช้ฟังก์ชัน ฉันไม่รู้ว่าเครื่องหมายคำพูดคู่ขยายตัวแปรในกรณีนี้
Mechaflash

echo hello | grep fooดูเหมือนจะไม่ผิดพลาดสำหรับฉัน ฉันเข้าใจอะไรผิดไปหรือเปล่า?
geotheory

@geotheory ในระบบของฉันgrepมีสถานะการออกเป็น 0 หากมีการจับคู่ 1 หากไม่มีการจับคู่และ> 1 สำหรับข้อผิดพลาด คุณสามารถตรวจสอบพฤติกรรมในระบบของคุณด้วยecho hello | grep foo; echo $?
Patrick

ไม่มีคุณขวามันเป็นข้อผิดพลาด :)
geotheory

คุณไม่จำเป็นต้องใช้ -e บนบรรทัดการเรียกใช้เพื่อทำให้เกิดข้อผิดพลาดเมื่อคำสั่งล้มเหลว? นั่นคือ: #! / bin / bash -e?
ทิมเบิร์ด

14

นอกจากนี้คุณยังสามารถใช้ bash builtin ใน '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

6

ฉันชอบคำตอบของ @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

นี่จะเป็นการดียิ่งขึ้นหากใช้$(caller)ข้อมูลเพื่อให้บริบทแม้ว่าความล้มเหลวไม่ได้อยู่ในสคริปต์ปัจจุบัน แต่เป็นการนำเข้าอย่างใดอย่างหนึ่ง แม้ว่าจะดีมาก!
tricasse

2

แรงบันดาลใจจากคำตอบอื่น ๆ นี่คือตัวจัดการข้อผิดพลาดตามบริบทที่ง่ายขึ้น:

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

คุณสามารถใช้ awk แทนหางและหัวได้ถ้าต้องการ


1
มีเหตุผลที่คำตอบอื่นให้บริบทโดย 3 บรรทัดด้านบนและ 3 บรรทัดด้านล่างบรรทัดที่ละเมิด - จะเกิดอะไรขึ้นถ้าข้อผิดพลาดเล็ดลอดออกมาจากเส้นต่อเนื่อง?
iruvar

@iruvar เข้าใจสิ่งนี้ แต่ฉันไม่ต้องการบริบทเพิ่มเติมใด ๆ บริบทหนึ่งบรรทัดนั้นง่ายอย่างที่ได้รับและเพียงพอตามที่ฉันต้องการ
sanmai

ตกลงเพื่อนของฉัน +1
iruvar

0

นี่เป็นอีกเวอร์ชั่นที่ได้แรงบันดาลใจจาก @sanmai และ @unpythonic มันแสดงบรรทัดสคริปต์รอบข้อผิดพลาดพร้อมหมายเลขบรรทัดและสถานะทางออก - ใช้ส่วนท้ายและส่วนหัวซึ่งดูง่ายกว่าโซลูชัน 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)

0

เป็นไปได้ไหมที่จะรับสัญญาณ ERR ที่เป็นเส้นตรง?

ใช่LINENOและBASH_LINENOตัวแปรต่าง ๆ เป็นอาหารมื้อเย็นที่มีประโยชน์สำหรับการทำให้เกิดความล้มเหลวและเส้นที่นำไปสู่มัน

หรือบางทีฉันอาจจะทำผิดทั้งหมดนี้?

ไม่เหลือ-qตัวเลือกที่มี grep ...

echo hello | grep -q "asdf"

... ด้วย-qตัวเลือกที่grepจะกลับมา0สำหรับtrueและสำหรับ1 falseและใน Bash 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และสถานะการออก ( $?) ถูกส่งก่อนอื่นเพื่อรับคำสั่งที่ส่งคืนข้อผิดพลาดและที่สองเพื่อให้แน่ใจว่ากับดักไม่ได้ทริกเกอร์สถานะที่ไม่ใช่ข้อผิดพลาด

และในขณะที่คนอื่นอาจไม่เห็นด้วยฉันพบว่ามันง่ายกว่าในการสร้างอาเรย์เอาท์พุทและใช้ 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

ดังที่แสดงโดยตัวอย่างเหล่านี้และอื่น ๆใน Stack Overflow มีหลายวิธีในการสร้างตัวช่วยการดีบักโดยใช้ยูทิลิตี้ในตัว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.