Tuzak, ERR ve hata satırını yankı


30

Tüm hatalarda işlev çağırmak için Tuzak kullanarak bazı hata raporları oluşturmaya çalışıyorum:

Trap "_func" ERR

ERR sinyalinin hangi hatta gönderildiğini bulmak mümkün müdür? Kabuk bash.

Bunu yaparsam hangi komutun kullanıldığını okuyabilir ve rapor edebilirim ve bazı işlemleri yapabilirim / yapabilirim.

Ya da belki de tamamen yanlış yapıyorum?

Aşağıdakilerle test ettim:

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

echo hello | grep "asdf"

Ve $LINENO2 döndürüyor. Çalışmıyor.


Bash hata ayıklayıcı komut dosyasına bakabilirsiniz bashdb. trapİstenen bağlamda değerlendirilen değişkenleri içerebilecek ilk argümanın olduğu anlaşılmaktadır . Öyleyse trap 'echo $LINENO' ERR'çalışmalı.
bağışlar başarılı bir şekilde

hmm sadece bu kötü bir yankı ile denedi | grep komutu ve Trap ifadesinin satırını döndürür. Fakat bashdb'ye bir göz atacağım
Mechaflash

Özür dilerim ... Orijinal soruma yerel bir çözüme ihtiyacım olduğunu belirtmedim. Soruyu değiştirdim.
Mechaflash

Üzgünüm, örnek hattını borked: trap 'echo $LINENO' ERR. İlk argüman traphepsi mi echo $LINENOhardquoted. Bu bash içinde.
bağışlar başarılı bir şekilde

5
@Machaflash trap 'echo $LINENO' ERRTek tırnaklı, çift tırnaklı olmak zorundaydı . Yazdığınız komutla, $LINENOsatır 2 ayrıştırıldığında genişletilir, bu nedenle tuzak echo 2(veya daha doğrusu ECHO 2çıktısı olur bash: ECHO: command not found).
Gilles 'SO- kötülük olmayı'

Yanıtlar:


61

Yorumlarda da belirtildiği gibi alıntılarınız yanlıştır. $LINENOBindirme çizgisi ilk ayrıştırıldığında genişletilmemesini önlemek için tek tırnaklara ihtiyacınız vardır .

Bu çalışıyor:

#! /bin/bash

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

trap 'err_report $LINENO' ERR

echo hello | grep foo  # This is line number 9

Çalıştırılıyor:

 $ ./test.sh
 Error on line 9

İşlev çağrısı ile örnek için teşekkürler. Çift tırnak bu durumda değişkenini genişlettiğini bilmiyordum.
Mechaflash

echo hello | grep foobenim için hata atıyor gibi görünmüyor. Bir şeyi yanlış mı anlıyorum?
geotheory

@geotheory Sistemimde grepbir eşleşme varsa 0, bir eşleşme yoksa 1 ve bir hata için> 1 çıkış durumu var. Sisteminizdeki davranışı 23: echo hello | grep foo; echo $?
Patrick

Hayır haklısın bu bir hatadır :)
geotheory

Komut başarısızlığında hataya neden olmak için, çağrı hattında -e kullanmanıza gerek yok mu? Bu: #! / Bin / bash -e?
Tim Bird

14

Bash yerleşik 'arayan' da kullanabilirsiniz:

#!/bin/bash

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

trap err_report ERR

echo hello | grep foo

dosya adını da yazdırır:

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

6

Yukarıda @Mat tarafından verilen cevabı çok beğendim. Buna dayanarak, hata için biraz daha bağlam veren küçük bir yardımcı yazdım:

Hataya neden olan satır için komut dosyasını inceleyebiliriz:

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

İşte küçük bir test betiğinde:

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

Çalıştırdığımızda şunu anlıyoruz:

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

Bu $(caller), hata mevcut komut dosyasında olmasa bile, içe aktarma işlemlerinden birinde olsa bile, içeriği vermek için verilerini kullanmak daha iyi olurdu . Yine de çok güzel!
Tricasse

2

Diğer yanıtlardan esinlenerek, daha basit bir bağlamsal hata işleyicisi:

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

Ayrıca edebilirsiniz kuyruk & başın yerine awk kullanmak gerekirse.


1
Diğer cevabın üst üste 3 ve üst üste 3 çizgiyle bağlam sağlamasının bir nedeni var - hata devam hattından kaynaklanıyorsa?
iruvar

@iruvar bu anlaşıldı, ancak bu ekstra bağlamdan hiçbirine ihtiyacım yok; o alır gibi bağlamın bir satır basit olarak, ve yeterli olarak ihtiyacım kadar
sanmai

Tamam arkadaşım, + 1
iruvar

1

İşte @sanmai ve @ unpythonic esinlenerek başka bir sürümü. Hatanın etrafındaki komut satırlarını, satır numaralarını ve çıkış durumunu gösterir - tail & head komutunu awk çözümünden daha basit görünüyor.

Bunu okunabilirlik için burada iki satır olarak gösterme - eğer tercih ediyorsanız bu satırları bir satırda birleştirebilirsiniz (koruyarak ;):

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

Bu set -euo pipefail( gayri resmi katı modda ) ile oldukça iyi çalışır - herhangi bir tanımsız değişken hatası, ERRsözde sinyale ateş etmeden satır numarası verir , ancak diğer durumlar bağlam gösterir.

Örnek çıktı:

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 sinyalinin hangi hatta gönderildiğini bulmak mümkün müdür?

Evet LINENOve BASH_LINENOdeğişkenler akşam yemeğinde başarısızlık hattını ve buna yol açan satırları almak için kullanışlıdır.

Ya da belki de tamamen yanlış yapıyorum?

Hayır, sadece -qgrep seçeneğiyle eksik seçenek ...

echo hello | grep -q "asdf"

... ile -qseçeneği grepdönecektir 0için trueve 1için false. Ve Bash'de trapdeğil Trap...

trap "_func" ERR

... doğal bir çözüme ihtiyacım var ...

İşte size biraz daha döngüsel karmaşıklığı olan şeyleri hata ayıklamak için yararlı bulabileceğiniz bir tuzak ...

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

... ve yukarıdaki fonksiyon tuzağının nasıl ayarlanacağına dair ince farkları ortaya çıkarmak için örnek bir komut dosyası ...

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'in sürüm 4+ üzerinde test edildiği yukarıdakiler, dört yaşından önceki sürümler için bir şeye ihtiyaç duyulursa bir yorum bırakın ya da en az dört sürümü olan sistemlerdeki arızaları yakalayamazsa bir Sorun Açın .

Ana paket servis yerleri ...

set -E -o functrace
  • -Eiçin işlevler içindeki hatalara neden olur kabarcık up

  • -o functrace Bir işlev içinde bir şey başarısız olduğunda nedenleri daha ayrıntılı bilgi sağlar

trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
  • Tek tırnak, işlev çağrısı etrafında kullanılır ve çift tırnak, bağımsız değişkenlerin etrafındadır.

  • Mevcut değerlerin yerine referanslar verilir LINENOve BASH_LINENObu, tuzağa bağlı olan sonraki sürümlerinde kısaltılabilir, ancak son başarısızlık çizgisi bunu çıktı haline getirir.

  • Değerleri BASH_COMMANDve çıkış durumu ( $?) ilk hata döndürdü komutu almak için geçti ve ikinci tuzak olmayan hata durumları ile ilgili tetiklemez sağlamaktan

Ve diğerleri aynı fikirde olmayabilirken, bir çıktı dizisi oluşturmanın ve her dizi öğesini kendi satırına yazdırmak için printf kullanmanın daha kolay olduğunu düşünüyorum ...

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

... ayrıca >&2sondaki bit, hataların olması gerektiği yere gitmesine neden olur (standart hata) ve sadece hataların yakalanmasına izin verir ...

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

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

Bunlar ve Yığın Taşması ile ilgili diğer örnekler tarafından gösterildiği gibi , yerleşik yardımcı programları kullanarak bir hata ayıklama yardımı oluşturmanın birçok yolu vardır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.