it-swarm.com.de

Ausführungsablaufverfolgung für Echo-Befehl unterdrücken?

Ich führe Shell-Skripte von Jenkins aus, wodurch Shell-Skripte mit den Shebang-Optionen #!/bin/sh -ex gestartet werden.

Laut Bash Shebang for Dummies? veranlasst -x "die Shell, einen Ausführungs-Trace zu drucken", was für die meisten Zwecke großartig ist - mit Ausnahme von Echos:

echo "Message"

erzeugt die Ausgabe

+ echo "Message"
Message

das ist ein bisschen überflüssig und sieht ein bisschen seltsam aus. Gibt es eine Möglichkeit, -x aktiviert zu lassen, aber nur die Ausgabe

Message

anstelle der beiden obigen Zeilen, z. indem Sie dem Echo-Befehl ein spezielles Befehlszeichen voranstellen oder die Ausgabe umleiten?

25
Christian

Wenn Sie Alligatoren tragen, können Sie leicht vergessen, dass das Ziel darin bestand, den Sumpf zu entwässern.- beliebtes Sprichwort

Die Frage bezieht sich auf echoNAME _, und doch haben sich die meisten Antworten bisher darauf konzentriert, wie ein set +x-Befehl eingeschleust werden kann. Es gibt eine viel einfachere, direktere Lösung:

{ echo "Message"; } 2> /dev/null

(Ich bestätige, dass ich möglicherweise nicht an den { …; } 2> /dev/null gedacht hätte, wenn ich ihn in den vorherigen Antworten nicht gesehen hätte.)

Dies ist etwas umständlich, aber wenn Sie einen Block aufeinanderfolgender echo-Befehle haben, müssen Sie dies nicht für jeden einzeln tun:

{
  echo "The quick brown fox"
  echo "jumps over the lazy dog."
} 2> /dev/null

Beachten Sie, dass Sie bei Zeilenumbrüchen keine Semikolons benötigen.

Sie können den Tippaufwand verringern, indem Sie kenorbs Idee verwenden, /dev/null dauerhaft in einem nicht standardmäßigen Dateideskriptor (z. B. 3) zu öffnen und dann 2>&3 anstelle von zu sagen 2> /dev/null die ganze Zeit.


Die ersten vier Antworten zum Zeitpunkt dieses Schreibens erfordern, dass Sie etwas Besonderes tun (und in den meisten Fällen umständlich) jedes Mal , wenn Sie einen echomachen. Wenn Sie wirklich möchten, dass all echodie Ausführungsablaufverfolgung unterdrückt (und warum nicht?), Können Sie dies global tun, ohne viel Code zu mischen. Erstens habe ich festgestellt, dass Aliase nicht zurückverfolgt werden:

$ myfunc()
> {
>     date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016  0:00:00 AM           # Happy Halloween!
$ myfunc
+ myfunc                                # Note that function call is traced.
+ date
Mon, Oct 31, 2016  0:00:01 AM
$ myalias
+ date                                  # Note that it doesn’t say  + myalias
Mon, Oct 31, 2016  0:00:02 AM

(Beachten Sie, dass die folgenden Skriptausschnitte funktionieren, wenn Shebang #!/bin/sh ist, auch wenn /bin/sh ein Link zu bash ist. Wenn Shebang #!/bin/bash ist, müssen Sie jedoch einen shopt -s expand_aliases-Befehl hinzufügen, damit Aliase in einem Skript funktionieren.)

Also für meinen ersten Trick:

alias echo='{ set +x; } 2> /dev/null; builtin echo'

Wenn wir jetzt echo "Message" sagen, rufen wir den Alias ​​auf, der nicht verfolgt wird. Der Alias ​​deaktiviert die Trace-Option und unterdrückt gleichzeitig die Trace-Nachricht aus dem Befehl set(unter Verwendung der Technik, die zuerst in der Antwort von user5071535 angegeben wurde). Anschließend wird der Befehl ausgeführt tatsächlicher echobefehl. Auf diese Weise erhalten wir einen ähnlichen Effekt wie bei der Antwort von user5071535, ohne den Code unter every echobearbeiten zu müssen. Der Trace-Modus bleibt jedoch deaktiviert. Wir können keinen set -x in den Alias ​​einfügen (oder zumindest nicht so einfach), da ein Alias ​​nur das Ersetzen eines Wortes durch eine Zeichenfolge ermöglicht. Kein Teil der Alias-Zeichenfolge kann in den Befehl nach den Argumenten (z. B. "Message") eingefügt werden. So zum Beispiel, wenn das Skript enthält

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date

die Ausgabe wäre

+ date
Mon, Oct 31, 2016  0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016  0:00:04 AM           # Note that it doesn’t say  + date

daher müssen Sie die Trace-Option nach der Anzeige von Nachrichten immer noch wieder aktivieren - jedoch nur einmal nach jedem Block aufeinanderfolgender echoname__-Befehle:

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date


Es wäre schön , wenn wir den set -x nach einem echoautomatisch machen könnten - und das können wir mit etwas mehr Trick. Aber bevor ich das präsentiere, denke ich darüber nach. Das OP beginnt mit Skripten, die einen #!/bin/sh -ex Shebang verwenden. Implizit könnte der Benutzer die xaus dem Shebang entfernen und ein Skript haben, das normal funktioniert, ohne die Ausführung zu verfolgen. Es wäre schön, wenn wir eine Lösung entwickeln könnten, die diese Eigenschaft beibehält. Die ersten Antworten hier schlagen fehl, da sie die Rückverfolgung nach echoname__-Anweisungen bedingungslos aktivieren, unabhängig davon, ob sie bereits aktiviert war. Diese Antwort erkennt dieses Problem auffällig nicht, da sie die Ausgabe von echodurch eine Ablaufverfolgungsausgabe ersetzt. Daher verschwinden alle Nachrichten, wenn die Ablaufverfolgung deaktiviert wird. Ich werde nun eine Lösung vorstellen, die die Rückverfolgung nach einer echo-Anweisung bedingt - nur aktiviert, wenn sie bereits aktiviert war. Die Herabstufung auf eine Lösung, die das bedingungslose Zurückverfolgen aktiviert, ist trivial und wird als Übung belassen.

alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
        builtin echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}

$- ist die Optionsliste; eine Verkettung der Buchstaben, die allen festgelegten Optionen entsprechen. Wenn zum Beispiel die Optionen eund xfestgelegt sind, ist $- ein Durcheinander von Buchstaben, das eund xenthält. Mein neuer Alias ​​(oben) speichert den Wert von $-, bevor die Ablaufverfolgung deaktiviert wird. Wenn die Ablaufverfolgung deaktiviert ist, wird die Steuerung in eine Shell-Funktion umgewandelt. Diese Funktion führt die eigentliche echoaus und prüft dann, ob die Option xbeim Aufrufen des Alias ​​aktiviert war. Wenn die Option aktiviert war, wird sie durch die Funktion wieder aktiviert. Wenn es ausgeschaltet war, lässt die Funktion es ausgeschaltet.

Sie können die obigen sieben Zeilen (acht, wenn Sie einen shopteinfügen) am Anfang des Skripts einfügen und den Rest in Ruhe lassen.

Dies würde es Ihnen erlauben

  1. eine der folgenden Shebang-Zeilen verwenden:
    #!/bin/sh -ex 
     #!/bin/sh -e 
     #!/bin/sh –x
    oder einfach nur
    #!/bin/sh
    und es sollte wie erwartet funktionieren.
  2. code wie haben
    (Shebang)befehl1befehl2befehl3
     set -x 
    befehl4befehl5befehl6
     setze + x 
    befehl7befehl8befehl9
    und
    • Die Befehle 4, 5 und 6 werden verfolgt - es sei denn, einer von ihnen ist ein echoname__. In diesem Fall wird er ausgeführt, aber nicht verfolgt. (Aber auch wenn Befehl 5 ein echoist, wird Befehl 6 trotzdem verfolgt.)
    • Befehle 7, 8 und 9 werden nicht verfolgt. Auch wenn Befehl 8 ein echoist, wird Befehl 9 immer noch nicht verfolgt.
    • Die Befehle 1, 2 und 3 werden nachverfolgt (wie 4, 5 und 6) oder nicht (wie 7, 8 und 9), je nachdem, ob der Shebang xenthält.

P.S. Ich habe festgestellt, dass ich auf meinem System das Schlüsselwort builtinin meiner mittleren Antwort weglassen kann (das ist nur ein Alias ​​für echoname__). Das ist nicht überraschend. Bash (1) sagt, dass während der Alias-Erweiterung ...

… Ein Wort, das mit einem Alias ​​identisch ist, der erweitert wird, wird kein zweites Mal erweitert. Dies bedeutet, dass man zum Beispiel einen Alias ​​lsNAME _ bis ls -F eingeben kann und bash nicht versucht, den Ersetzungstext rekursiv zu erweitern.

Es überrascht nicht, dass die letzte Antwort (die mit echo_and_restore) fehlschlägt, wenn das Schlüsselwort builtinweggelassen wird1. Aber seltsamerweise funktioniert es, wenn ich builtinlösche und die Reihenfolge ändere:

echo_and_restore() {
        echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'

__________
1Es scheint zu undefiniertem Verhalten zu führen. Ich habe gesehen

  • eine Endlosschleife (wahrscheinlich wegen unbegrenzter Rekursion),
  • eine /dev/null: Bad address Fehlermeldung und
  • ein Core-Dump.
16
G-Man

Ich habe eine Teillösung über InformIT gefunden:

#!/bin/bash -ex
set +x; 
echo "Shell tracing is disabled here"; set -x;
echo "but is enabled here"

ausgänge

set +x; 
Shell tracing is disabled here 
+ echo "but is enabled here"
but is enabled here

Leider klingt das immer noch nach set +x, aber danach ist es zumindest ruhig. Es ist also zumindest eine Teillösung des Problems.

Aber gibt es vielleicht einen besseren Weg, dies zu tun? :)

11
Christian

Auf diese Weise können Sie Ihre eigene Lösung verbessern, indem Sie die Ausgabe von set +x entfernen:

#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "Shell tracing is disabled here"; set -x;
echo "but is enabled here"
2
user5071535

Setzen Sie set +x in die Klammern, damit es nur für den lokalen Bereich gilt.

Zum Beispiel:

#!/bin/bash -x
exec 3<> /dev/null
(echo foo1 $(set +x)) 2>&3
($(set +x) echo foo2) 2>&3
( set +x; echo foo3 ) 2>&3
true

würde ausgeben:

$ ./foo.sh 
+ exec
foo1
foo2
foo3
+ true
2
kenorb

Ich mag die umfassende und gut erläuterte Antwort von g-man und halte sie für die beste, die bisher bereitgestellt wurde. Es kümmert sich um den Kontext des Skripts und erzwingt keine Konfigurationen, wenn sie nicht benötigt werden. Wenn Sie diese Antwort also zuerst lesen, prüfen Sie sie, denn der ganze Verdienst ist da.

In dieser Antwort fehlt jedoch ein wichtiger Teil: Die vorgeschlagene Methode funktioniert nicht für einen typischen Anwendungsfall, d. H. Das Melden von Fehlern:

COMMAND || echo "Command failed!"

Aufgrund der Art und Weise, wie der Alias ​​aufgebaut ist, wird dies auf erweitert

COMMAND || { save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore "Command failed!"

und Sie haben es erraten, echo_and_restore wird immerbedingungslos ausgeführt. Da der Teil set +x nicht ausgeführt wurde, wird auch der Inhalt dieser Funktion gedruckt.

Das Ändern des letzten ; in && würde auch nicht funktionieren, da in Bash || und &&linksassoziativ sind.

Ich habe eine Modifikation gefunden, die für diesen Anwendungsfall funktioniert:

echo_and_restore() {
    echo "$(cat -)"
    case "$save_flags" in
        (*x*) set -x
    esac
}
alias echo='({ save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore) <<<'

Es verwendet eine Subshell (den Teil (...)), um alle Befehle zu gruppieren, und übergibt dann die Eingabezeichenfolgestdinals Here String (das <<<-Ding), das dann von cat - gedruckt wird. Der - ist optional, aber Sie wissen, dass "explicit besser ist als implicit".

Sie können cat - auch direkt ohne dasechoverwenden. Dies gefällt mir jedoch, da ich der Ausgabe andere Zeichenfolgen hinzufügen kann. Zum Beispiel benutze ich es normalerweise so:

BASENAME="$(basename "$0")"  # Complete file name
...
echo "[${BASENAME}] $(cat -)"

Und jetzt funktioniert es wunderbar:

false || echo "Command failed"
> [test.sh] Command failed
1
j1elo

Der Ausführungs-Trace geht an stderrname__. Filtern Sie ihn folgendermaßen:

./script.sh 2> >(grep -v "^+ echo " >&2)

Einige Erklärungen, Schritt für Schritt:

  • stderrwird umgeleitet ... - 2>
  • ... zu einem Befehl. - >(…)
  • grepist der Befehl ...
  • … Was einen Zeilenanfang erfordert… - ^
  • … Gefolgt von + echo
  • … Dann kehrt grepdie Übereinstimmung um… - -v
  • … Und das verwirft alle Zeilen, die Sie nicht wollen.
  • Das Ergebnis würde normalerweise zu stdoutgehen; Wir leiten es an stderrweiter, wo es hingehört. - >&2

Das Problem ist (ich denke), dass diese Lösung die Streams desynchronisieren kann. Aufgrund der Filterung kann es sein, dass stderretwas zu spät in Bezug auf stdoutist (wo echoausgegeben wird) standardmäßig). Um dies zu beheben, können Sie zuerst die Streams verbinden, wenn Sie nichts dagegen haben, beide in stdoutzu haben:

./script.sh > >(grep -v "^+ echo ") 2>&1

Sie können eine solche Filterung in das Skript selbst einbauen, aber dieser Ansatz ist mit Sicherheit anfällig für Desynchronisation (dh er ist in meinen Tests aufgetreten: Ausführungsablaufverfolgung von a Der Befehl wird möglicherweise nach der Ausgabe von echoname__) angezeigt.

Der Code sieht folgendermaßen aus:

#!/bin/bash -x

{
 # original script here
 # …
} 2> >(grep -v "^+ echo " >&2)

Führen Sie es ohne Tricks aus:

./script.sh

Verwenden Sie erneut > >(grep -v "^+ echo ") 2>&1, um die Synchronisation auf Kosten des Beitritts zu den Streams aufrechtzuerhalten.


Ein anderer Ansatz. Sie erhalten eine "etwas redundante" und seltsam aussehende Ausgabe, da Ihr Terminal stdoutund stderrmischt . Diese beiden Ströme sind aus einem bestimmten Grund unterschiedliche Tiere. Prüfen Sie, ob die Analyse von stderrnur Ihren Anforderungen entspricht. stdoutverwerfen:

./script.sh > /dev/null

Wenn Sie in Ihrem Skript eine echo-Debug-/Fehlermeldung für stderrhaben, können Sie die Redundanz auf die oben beschriebene Weise beseitigen. Voller Befehl:

./script.sh > /dev/null 2> >(grep -v "^+ echo " >&2)

Dieses Mal arbeiten wir nur mit stderrname__, sodass die Desynchronisation kein Problem mehr darstellt. Leider wird auf diese Weise weder eine Ablaufverfolgung noch eine Ausgabe von echoangezeigt, die auf stdout(falls vorhanden) gedruckt wird. Wir könnten versuchen, unseren Filter neu zu erstellen, um die Umleitung (>&2) zu erkennen, aber wenn Sie sich echo foobar >&2, echo >&2 foobar und echo "foobar >&2" ansehen, werden Sie wahrscheinlich zustimmen, dass die Dinge kompliziert werden.

Viel hängt von den Echos in Ihren Skripten ab. Überlegen Sie zweimal, bevor Sie einen komplexen Filter implementieren, da er möglicherweise nach hinten losgeht. Es ist besser, ein bisschen Redundanz zu haben, als versehentlich wichtige Informationen zu übersehen.


Anstatt die Ausführungsablaufverfolgung eines echozu verwerfen, können wir seine Ausgabe verwerfen - und jede Ausgabe außer den Ablaufverfolgungen. Versuchen Sie Folgendes, um nur Ausführungsprotokolle zu analysieren:

./script.sh > /dev/null 2> >(grep "^+ " >&2)

Narrensicher? Überlegen Sie, was passiert, wenn das Skript echo "+ rm -rf --no-preserve-root /" >&2 enthält. Jemand könnte einen Herzinfarkt bekommen.


Und schlussendlich…

Zum Glück gibt es BASH_XTRACEFD Umgebungsvariable. Von man bash:

BASH_XTRACEFD
Wenn eine Ganzzahl festgelegt wird, die einem gültigen Dateideskriptor entspricht, schreibt bash die Ablaufverfolgungsausgabe, die bei Aktivierung von set -x generiert wird, in diesen Dateideskriptor.

Wir können es so benutzen:

(exec 3>trace.txt; BASH_XTRACEFD=3 ./script.sh)
less trace.txt

Beachten Sie, dass die erste Zeile eine Subshell erzeugt. Auf diese Weise bleiben weder der Dateideskriptor noch die in der aktuellen Shell zugewiesene Variable gültig.

Dank BASH_XTRACEFD können Sie Spuren ohne Echos und andere Ausgaben analysieren, unabhängig davon, um was es sich handelt. Es ist nicht genau das, was Sie wollten, aber meine Analyse lässt mich denken, dass dies (im Allgemeinen) der richtige Weg ist.

Natürlich können Sie eine andere Methode verwenden, insbesondere wenn Sie stdoutund/oder stderrzusammen mit Ihren Traces analysieren müssen. Sie müssen sich nur daran erinnern, dass es bestimmte Einschränkungen und Fallstricke gibt. Ich habe versucht, einige von ihnen zu zeigen.

0

In einem Makefile können Sie das Symbol @ verwenden.

Beispielverwendung: @echo 'message'

Aus diesem GNU doc:

Wenn eine Zeile mit "@" beginnt, wird das Echo dieser Zeile unterdrückt. Das "@" wird verworfen, bevor die Zeile an die Shell übergeben wird. Normalerweise verwenden Sie dies für einen Befehl, dessen einzige Auswirkung darin besteht, etwas zu drucken, z. B. einen Echo-Befehl, um den Fortschritt durch das Makefile anzuzeigen.

0
derek