Falsches $ LINENO für eine gefangene Funktion

7

Ich schreibe ein Bash-Skript für mich, um das Schreiben von Skripten zu lernen. Irgendwann muss ich Trap hinzufügen, um unerwünschte Verzeichnisse und Dateien zu bereinigen, wenn das Skript beendet wird. Aus irgendeinem Grund, den ich nicht verstehe, ruft Trap die Reinigungsfunktion auf clean_a()- wenn das Skript beendet wird, zeigt aber $LINENOauf eine Zeile in der Reinigungsfunktion selbst, nicht in der Funktion archieve_it()- wenn das Skript beendet wird.

Erwartetes Verhalten:

  1. Skript ausführen
  2. Drücken Sie Ctrl+C
  3. Trap Caches Ctrl+ Cund ruft clean_a()Funktion auf
  4. clean_a()Die Funktion gibt die Zeilennummer wieder, die Ctrl+ Cgedrückt wird. Lassen Sie es Zeile 10 in sein archieve_it().

Was passiert eigentlich:

  1. Skript ausführen
  2. Drücken Sie Ctrl+C
  3. Trap Caches Ctrl+ Cund ruft clean_a()Funktion auf
  4. clean_a()gibt eine irrelevante Zeilennummer wieder. Sagen wir, Zeile 25 in clean_a()Funktion.

Hier ist ein Beispiel als Teil meines Skripts:

archieve_it () {
  trap 'clean_a $LINENO $BASH_COMMAND'\
                SIGHUP SIGINT SIGTERM SIGQUIT
  for src in ${sources}; do
   mkdir -p "${dest}${today}${src}"
   if [[ "$?" -ne 0 ]] ; then
    error "Something!" 
   fi
   rsync "${options}" \
         --stats -i \
         --log-file="${dest}${rsync_log}" \
         "${excludes}" "${src}" "${dest}${today}${src}"
  done
}
clean_a () {
  error "something!
  line: $LINENO
  command: $BASH_COMMAND
  removing ${dest}${today}..."
  cd "${dest}"
  rm -rdf "${today}"
  exit "$1"
}

PS: Original - Skript zu sehen ist hier . Definitionen und Variablennamen sind auf Türkisch. Wenn es benötigt wird, kann ich alles ins Englische übersetzen.

BEARBEITEN: Ich ändere das Skript so gut ich kann gemäß der Erklärung von @ mikeserv wie folgt:

#!/bin/bash
PS4='DEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO $BASH_COMMAND'\
                SIGHUP SIGINT SIGTERM SIGQUIT
  ..
}
clean_a () {
  error " ...
  line: $LINENO $LASTNO
  ..."
}

Wenn ich nun ein Skript mit ausführe set -xund es mit Ctrl+ Cbeende, wird die richtige Zeilennummer ausgegeben, wie unten zu sehen ist:

 DDEBUG: 1 : clean_a 1 336 rsync '"${options}"' ...

In der clean_a()Funktion wird der Wert von $LASTNOjedoch als 1 gedruckt.

 line: 462 1

Hat es etwas mit dem Fehler zu tun, der von @Arkadiusz Drabczyk angezeigt wird?

EDIT2 : Ich habe das Skript genau so geändert, wie es mir @mikesrv empfohlen hat. Aber $ LASTNO hat 1 als Wert der Zeile zurückgegeben, als das Skript beendet wurde (es hätte 337 sein sollen).

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
                SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
clean_a () {
  error " ...
  line: $LASTNO $LINENO
  ..."
} 2>&1

Wenn ich ein Skript ausführe und es mit Ctrl+ Cbeende, während rsync ausgeführt wurde, erhalte ich folgende Ausgabe:

^^MDEBUG: 1 : clean_a '337 1 rsync "${options}" --delete-during ...
...
line: 1 465

Wie Sie sehen können, ist der Wert von $ LASTNO 1.

Während ich versuchte herauszufinden, wo das Problem liegt, schrieb ich eine andere Funktion - testing- unter Verwendung des Parametersubstitutionsformats ${parameter:-default}. Das Skript stellte sich also wie folgt heraus:

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'testing "$LASTNO $LINENO $BASH_COMMAND"'\
                 SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
testing() {
  echo -e "${1:-Unknown error!}"
  exit 1
} 2>&1

Wenn ich nun ein Skript ausführe und Ctrl+ drücke C, erhalte ich folgende Ausgabe:

^^MDEBUG: 1 : testing '337 1 rsync "${options}" --delete-during ...
337 1 rsync "${options}" --delete-during ... 

337 zeigt auf die Linie, als ich Ctrl+ drückte C, während rsync lief.

Für einen weiteren Test habe ich versucht, eine clear_aFunktion wie diese zu schreiben :

clear_a () {
  echo -e " $LASTNO $LINENO"
}

und $ LASTNO gab immer noch 1 zurück.

Das bedeutet also, dass wir beim Beenden des Skripts die korrekte Zeilennummer erhalten können, wenn wir die Parametersubstitution verwenden?

EDIT3 Es scheint, dass ich die Erklärung von @ mikeserv in EDIT2 falsch angewendet habe. Ich habe meinen Fehler korrigiert. Der Positionsparameter "$1sollte in der Funktion durch $ LASTNO clear_aersetzt werden.

Hier ist das Skript, das so funktioniert, wie ich es haben möchte:

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
                SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
clean_a () {
  error " ...
  line: $1
  ..."
} 2>&1

Wenn das Skript beendet wird, werden trapdas $LASTNOerste Argument, das $LINENOzweite Argument und das dritte Argument ausgewertet und $BASH_COMMANDdie Werte an die clear_aFunktion übergeben. Schließlich drucken wir $ LASTNO mit $1der Zeilennummer, mit der das Skript beendet wird.

numand
quelle
Hallo! Über Punkt 4 - "irrelevante Zeilennummer" - auf meinem System wird immer 1 zurückgegeben. In Bash 4 hat sich etwas geändert - siehe hier . Welche Bash-Version hast du?
Arkadiusz Drabczyk
Entschuldigung, ich meinte "echo $ 1" gibt mir immer 1, nicht "echo $ LINENO"
Arkadiusz Drabczyk
Hallo, ich benutze GNU Bash, Version 4.3.22 (1) -release (x86_64-unknown-linux-gnu).
Nummer
@mikeserv, sorry für die Unannehmlichkeiten, mein Unrecht. Ich habe meinen Fehler korrigiert. Jetzt funktioniert es so, wie ich arbeiten möchte.
Nummer
Bash bietet auch ein BASH_LINENOArray, das wahrscheinlich genau das ist, was Sie wollen, anstatt nur LINENO.
dimo414

Antworten:

4

Ich denke, das Problem ist, dass Sie erwarten "$LINENO", Ihnen die Ausführungszeile für den letzten Befehl zu geben, die fast funktioniert, aber clean_a() auch eine eigene bekommt $LINENOund die Sie stattdessen tun sollten:

error "something!
line: $1
...

Aber selbst das würde wahrscheinlich nicht funktionieren, da ich davon ausgehe, dass nur die Zeile gedruckt wird, in der Sie die Einstellung vorgenommen haben trap.

Hier ist eine kleine Demo:

PS4='DEBUG: $LINENO : ' \
bash -x <<\CMD          
    trap 'fn "$LINENO"' EXIT             
    fn() { printf %s\\n "$LINENO" "$1"; }
    echo "$LINENO"
CMD

AUSGABE

DEBUG: 1 : trap 'fn "$LINENO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1
DEBUG: 2 : printf '%s\n' 2 1
2
1

Also wird das trapgesetzt, dann fn()definiert und dann echoausgeführt. Wenn die Shell die Ausführung ihrer Eingabe abgeschlossen hat, wird der EXITTrap ausgeführt und fnaufgerufen. Es wird ein Argument übergeben - das ist das der trapZeile $LINENO. fndruckt zuerst sein eigenes, $LINENOdann sein erstes Argument.

Ich kann mir einen Weg vorstellen, wie Sie das Verhalten erreichen können, das Sie erwarten, aber es bringt die Schale irgendwie durcheinander stderr:

PS4='DEBUG: $((LASTNO=$LINENO)) : ' \
bash -x <<\CMD
    trap 'fn "$LINENO" "$LASTNO"' EXIT
    fn() { printf %s\\n "$LINENO" "$LASTNO" "$@"; }
    echo "$LINENO"
CMD

AUSGABE

DEBUG: 1 : trap 'fn "$LINENO" "$LASTNO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1 3
DEBUG: 2 : printf '%s\n' 2 1 1 3
2
1
1
3

Es verwendet die $PS4Debug-Eingabeaufforderung der Shell , um $LASTNOin jeder ausgeführten Zeile zu definieren . Es ist eine aktuelle Shell-Variable, auf die Sie überall im Skript zugreifen können. Das bedeutet, dass Sie unabhängig von der Zeile, auf die gerade zugegriffen wird, auf die letzte Zeile des Skripts verweisen können, in dem ausgeführt wird $LASTNO. Wie Sie sehen, wird es natürlich mit einer Debug-Ausgabe geliefert. Sie können das 2>/dev/nullfür den Großteil der Ausführung des Skripts verschieben, und dann einfach 2>&1in clean_a()oder so.

Der Grund , warum Sie bekommen 1in $LASTNOist , denn das ist der letzte Wert , auf dem $LASTNOgesetzt wurde , weil das der letzte war $LINENOWert. Sie haben Ihre trapin der archieve_it()Funktion und so bekommt es seine eigene, $LINENOwie in der Spezifikation unten angegeben. Obwohl es bashdort sowieso nicht so aussieht, als ob es das Richtige tut, kann es auch daran liegen, dass trapdas Shell- INTSignal erneut ausgeführt werden muss und $LINENOdaher zurückgesetzt wird. In diesem Fall bin ich ein wenig verwirrt - wie es bashanscheinend ist.

Sie wollen nicht zu bewerten , $LASTNOin clean_a(), glaube ich. Besser wäre es in der zu bewerten trapund den Wert übergeben traperhält in $LASTNOdurch clean_a()als Argument. Vielleicht so:

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
    trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
        SIGHUP SIGINT SIGTERM SIGQUIT
    while :; do sleep 1; done
} 2>/dev/null
clean_a () { : "$@" ; } 2>&1

Versuchen Sie das - es sollte tun, was Sie wollen, denke ich. Oh - und beachten Sie, dass in PS4=^Mder ^Meine wörtliche Rückkehr ist - wie STRG + V ENTER.

Aus der POSIX-Shell-Spezifikation :

Wird von der Shell auf eine Dezimalzahl festgelegt, die die aktuelle fortlaufende Zeilennummer (beginnend mit 1) innerhalb eines Skripts oder einer Funktion darstellt, bevor jeder Befehl ausgeführt wird. Wenn der Benutzer zurücksetzt oder zurücksetzt LINENO, verliert die Variable möglicherweise ihre spezielle Bedeutung für die Lebensdauer der Shell. Wenn die Shell derzeit kein Skript oder keine Funktion ausführt, ist der Wert von LINENOnicht angegeben. Dieses Volume von IEEE Std 1003.1-2001 gibt die Auswirkungen der Variablen nur für Systeme an, die die Option User Portability Utilities unterstützen.

mikeserv
quelle
Vielen Dank @mikeserv für Ihren Kommentar und Ihre Bemühungen.
Nummer
@numand - Ich habe nur Ihre Bearbeitung und versucht, sie mit meiner eigenen zu adressieren. Hilft es?
Mikeserv
Leider hat es nicht geholfen. Aber ich habe etwas herausgefunden, das uns helfen kann.
Nummer
@numand was ist das? und was meinst du damit, dass es nicht geholfen hat? es funktioniert gut für mich
mikeserv
1
Ich danke dir. Ich schätze Ihre Bemühungen sehr.
Nummer
7

Die Lösung von mikeserv ist gut, aber er sagt zu Unrecht, dass fndie trapLinie überschritten wird, $LINENOwenn die Falle ausgeführt wird. Fügen trap ...Sie vorher eine Zeile ein und Sie werden sehen, dass diese fntatsächlich immer übergeben wird 1, unabhängig davon, wo die Falle deklariert wurde.

PS4='DEBUG: $LINENO : ' \
bash -x <<\EOF
    echo Foo
    trap 'fn "$LINENO"' EXIT             
    fn() { printf %s\\n "$LINENO" "$1"; }
    echo "$LINENO"
    exit
EOF

AUSGABE

DEBUG: 1 : echo Foo
Foo
DEBUG: 2 : trap 'fn "$LINENO"' EXIT
DEBUG: 4 : echo 4
4
DEBUG: 5 : exit
DEBUG: 1 : fn 1
DEBUG: 3 : printf '%s\n' 3 1
3
1

Da das erste Argument, das abgefangen werden fn "$LINENO"soll, in einfache Anführungszeichen gesetzt wird, $LINENOwird es genau dann erweitert , wenn EXIT ausgelöst wird, und sollte daher auf erweitert werden fn 5. Warum nicht? Tatsächlich war dies bis zu Bash-4.0 der Fall, als es absichtlich geändert wurde, sodass $ LINENO beim Auslösen des Traps auf 1 zurückgesetzt wird und daher auf erweitert wird fn 1. [Quelle] Das ursprüngliche Verhalten wird jedoch für ERR-Traps beibehalten, wahrscheinlich weil wie oft so etwas trap 'echo "Error at line $LINENO"' ERRverwendet wird.

#!/bin/bash

trap 'echo "exit at line $LINENO"' EXIT
trap 'echo "error at line $LINENO"' ERR
false
exit 0

AUSGABE

error at line 5
exit at line 1

getestet mit GNU Bash, Version 4.3.42 (1) -Veröffentlichung (x86_64-pc-linux-gnu)

Niklas Holm
quelle
Entschuldigen Sie die verspätete Antwort @Niklas Holm und bedanken Sie sich für diese wertvollen Informationen.
Nummer