Jak znaleźć numer linii w Bash, gdy wystąpił błąd?

21

Jak znaleźć numer linii w Bash, w którym wystąpił błąd?

Przykład

Tworzę następujący prosty skrypt z numerami linii, aby wyjaśnić, czego potrzebujemy. Skrypt skopiuje pliki z

cp $file1 $file2
cp $file3 $file4

Gdy jedno z cppoleceń nie powiedzie się, funkcja zakończy działanie z wyjściem 1 . Chcemy dodać funkcję do funkcji drukowania błędu również z numerem linii (na przykład 8 lub 12).

czy to możliwe?

Przykładowy skrypt

1 #!/bin/bash
2
3
4 function in_case_fail {
5 [[ $1 -ne 0 ]] && echo "fail on $2" && exit 1
6 }
7
8 cp $file1 $file2
9 in_case_fail $? "cp $file1 $file2"
10
11
12 cp $file3 $file4
13 in_case_fail $? "cp $file3 $file4"
14
Yael
źródło
Możesz użyć set -xi / lub set -vdo śledzenia tego, co zostało wykonane. Niezupełnie to, o co prosiłeś, ale prawdopodobnie też będzie pomocne.
Rolf

Odpowiedzi:

29

Zamiast używać twojej funkcji, zamiast tego użyłbym tej metody:

$ cat yael.bash
#!/bin/bash

set -eE -o functrace

file1=f1
file2=f2
file3=f3
file4=f4

failure() {
  local lineno=$1
  local msg=$2
  echo "Failed at $lineno: $msg"
}
trap 'failure ${LINENO} "$BASH_COMMAND"' ERR

cp -- "$file1" "$file2"
cp -- "$file3" "$file4"

Działa to poprzez pułapkę na ERR, a następnie wywołanie failure() funkcji z bieżącym numerem linii + wykonaną komendą bash.

Przykład

Tutaj nie podjęliśmy żadnych starań, aby tworzyć pliki, f1, f2, f3, lub f4. Po uruchomieniu powyższego skryptu:

$ ./yael.bash
cp: cannot stat f1’: No such file or directory
Failed at 17: cp -- "$file1" "$file2"

Nie udaje się, zgłaszając numer wiersza plus wykonane polecenie.

slm
źródło
14

Oprócz LINENOzawierającego bieżący numer linii, są też BASH_LINENOi FUNCNAME(iBASH_SOURCE tablice ), które zawierają nazwy funkcji i numery linii, z których są wywoływane.

Więc możesz zrobić coś takiego:

#!/bin/bash

error() {
        printf "'%s' failed with exit code %d in function '%s' at line %d.\n" "${1-something}" "$?" "${FUNCNAME[1]}" "${BASH_LINENO[0]}"
}

foo() {
        ( exit   0 ) || error "this thing"
        ( exit 123 ) || error "that thing"
}

foo

Uruchomienie, które zostanie wydrukowane

'that thing' failed with exit code 123 in function 'foo' at line 9.

Jeśli używasz set -elub trap ... ERRautomatycznie wykrywasz błędy, zauważ, że mają one pewne zastrzeżenia. Trudniej jest także dołączyć opis działania skryptu w tym czasie (tak jak w przykładzie), chociaż może to być bardziej użyteczne dla zwykłego użytkownika niż tylko numer wiersza.

Zobacz np. Te problemy z set -ei innymi:

ilkkachu
źródło
13

Bash ma wbudowaną zmienną, $LINENOktóra jest zastępowana bieżącym numerem wiersza w instrukcji, więc możesz to zrobić

in_case_fail $? "at $LINENO: cp $file1 $file2"

Możesz także spróbować użyć, trap ... ERRktóry uruchamia się, gdy polecenie nie powiedzie się (jeśli wynik nie zostanie przetestowany). Na przykład:

trap 'rc=$?; echo "error code $rc at $LINENO"; exit $rc' ERR

Jeśli takie polecenie cp $file1 $file2nie powiedzie się, otrzymasz komunikat o błędzie z numerem wiersza i wyjściem. Polecenie znajdziesz również w błędzie w zmiennej $BASH_COMMAND(choć nie ma żadnych przekierowań itp.).

meuh
źródło