Wie kann ich die if-Befehle von bash zusammen verwenden, um Befehle zu testen und zu finden?

25

Ich habe ein Verzeichnis mit Absturzprotokollen und möchte eine bedingte Anweisung in einem Bash-Skript verwenden, das auf einem find-Befehl basiert.

Die Protokolldateien werden in folgendem Format gespeichert:

/var/log/crashes/app-2012-08-28.log
/var/log/crashes/otherapp-2012-08-28.log

Ich möchte, dass die if-Anweisung nur dann true zurückgibt, wenn ein Absturzprotokoll für eine bestimmte App vorhanden ist, das in den letzten 5 Minuten geändert wurde. Der findBefehl, den ich verwenden würde, ist:

find /var/log/crashes -name app-\*\.log -mmin -5

Ich bin mir nicht sicher, wie ich das ifrichtig in eine Aussage einbauen soll . Ich denke, das könnte funktionieren:

if [ test `find /var/log/crashes -name app-\*\.log -mmin -5` ] then
 service myapp restart
fi

Es gibt einige Bereiche, in denen ich unklar bin:

  • Ich habe mir die if-Flags angesehen , bin mir aber nicht sicher, welche ich verwenden soll.
  • Benötige ich die testDirektive oder sollte ich nur direkt mit den Ergebnissen des Befehls find verfahren oder find... | wc -lstattdessen eine Zeilenzählung durchführen?
  • Nicht zu 100% notwendig, um diese Frage zu beantworten, sondern testzum Testen von Rückgabecodes, die von Befehlen zurückgegeben werden? Und sie sind irgendwie unsichtbar - außerhalb von stdout/ stderr? Ich habe die manSeite gelesen , bin mir aber immer noch ziemlich unklar, wann ich sie verwenden testund wie ich sie debuggen soll.
cwd
quelle
Die eigentliche Antwort auf den allgemeinen Fall lautet: Verwenden find ... -exec. Siehe auch die Beispielbefehle unter Warum wird die Ausgabe von find nicht ordnungsgemäß wiederholt?
Wildcard
@Wildcard - das löst leider nicht den allgemeinen Fall: Es funktioniert nicht, wenn es mehr als eine Übereinstimmung gibt und die Aktion nur einmal ausgeführt werden muss, und es funktioniert nicht, wenn Sie eine Aktion ausführen müssen, wenn es solche gibt keine Treffer. Ersteres kann mit gelöst werden ... -exec command ';' -quit, aber ich glaube nicht, dass es für Letzteres eine andere Lösung gibt, als das Ergebnis zu analysieren. Außerdem tritt in beiden Fällen das Hauptproblem beim Analysieren des Ergebnisses von find(dh der Unfähigkeit, Trennzeichen von Zeichen in Dateinamen zu unterscheiden) nicht auf, da in diesen Fällen keine Trennzeichen gefunden werden müssen.
Jules

Antworten:

27

[und testsind Synonyme (außer [erfordert ]), daher möchten Sie Folgendes nicht verwenden [ test:

[ -x /bin/cat ] && echo 'cat is executable'
test -x /bin/cat && echo 'cat is executable'

testGibt einen Exit-Status von Null zurück, wenn die Bedingung erfüllt ist, andernfalls ungleich Null. Dies kann tatsächlich durch ein beliebiges Programm ersetzt werden, um den Beendigungsstatus zu überprüfen. Dabei gibt 0 Erfolg und Nicht-Null Fehler an:

# echoes "command succeeded" because echo rarely fails
if /bin/echo hi; then echo 'command succeeded'; else echo 'command failed'; fi

# echoes "command failed" because rmdir requires an argument
if /bin/rmdir; then echo 'command succeeded'; else echo 'command failed'; fi

Alle obigen Beispiele testen jedoch nur den Exit-Status des Programms und ignorieren die Programmausgabe.

Für findmüssen Sie testen, ob eine Ausgabe generiert wurde. -ntestet auf eine nicht leere Zeichenkette:

if [[ -n $(find /var/log/crashes -name "app-*.log" -mmin -5) ]]
then
    service myapp restart
fi

Eine vollständige Liste der Testargumente können Sie help testüber die bashBefehlszeile aufrufen .

Wenn Sie verwenden bash(und nicht sh), können Sie verwenden [[ condition ]], was sich vorhersehbarer verhält, wenn sich in Ihrem Zustand Leerzeichen oder andere Sonderfälle befinden. Ansonsten ist es im Allgemeinen dasselbe wie beim Verwenden [ condition ]. Ich habe [[ condition ]]in diesem Beispiel verwendet, wie ich es mache, wann immer es möglich ist.

Ich habe auch `command`zu $(command), was auch im Allgemeinen ähnlich verhält, aber ist schöner mit verschachtelten Befehlen.

mrb
quelle
echokann scheitern: versuchen echo 'oops' > /dev/full.
Derobert
Diese Antwort schlägt alle um die Wurzel des Problems herum, vermeidet jedoch anmutig, genau zu erwähnen, was das ist.
Bahamat
9

findWird erfolgreich beendet, wenn keine Fehler aufgetreten sind. Sie können sich also nicht auf den Beendigungsstatus verlassen, um festzustellen, ob eine Datei gefunden wurde. Wie Sie bereits sagten, können Sie jedoch die Anzahl der gefundenen Dateien zählen und diese Zahl testen.

Es wäre ungefähr so:

if [ $(find /var/log/crashes -name 'app-*.log' -mmin -5 | wc -l) -gt 0 ]; then
    ...
fi

test(aka [) überprüft die Fehlercodes der Befehle nicht, es hat eine spezielle Syntax, um Tests durchzuführen, und wird dann mit einem Fehlercode von 0 beendet, wenn der Test erfolgreich war, oder 1, wenn dies nicht der Fall ist. Es ist ifderjenige, der den Fehlercode des Befehls überprüft, den Sie an ihn übergeben, und den Hauptteil darauf basierend ausführt.

Siehe man test(oder help test, wenn Sie verwenden bash) und help if(dito).

In diesem Fall wc -lwird eine Zahl ausgegeben. Wir verwenden testdie Option -gt, um zu testen, ob diese Anzahl größer als ist 0. Wenn dies der Fall ist, wird test(oder [) mit dem Beendigungscode zurückgegeben 0. ifinterpretiert diesen Exit-Code als Erfolg und führt den Code in seinem Körper aus.

angus
quelle
1

Das wäre

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)" ]; then

oder

if test -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)"; then

Die Befehle testund [ … ]sind genau das Gleiche. Der einzige Unterschied ist ihr Name und die Tatsache, [dass ein Abschluss ]als letztes Argument erforderlich ist . Verwenden Sie wie immer doppelte Anführungszeichen um die Befehlsersetzung, andernfalls wird die Ausgabe des findBefehls in Wörter aufgeteilt, und hier wird ein Syntaxfehler angezeigt, wenn mehr als eine übereinstimmende Datei vorhanden ist (und wenn keine Argumente vorhanden sind, [ -n ]ist dies wahr) , während Sie wollen, [ -n "" ]was falsch ist).

In ksh, bash und zsh, aber nicht in ash, können Sie auch verwenden, [[ … ]]die unterschiedliche Parsing-Regeln haben: [ist ein gewöhnlicher Befehl, wohingegen [[ … ]]es sich um ein anderes Parsing-Konstrukt handelt. Sie brauchen keine doppelten Anführungszeichen [[ … ]](obwohl sie nicht schaden). Du brauchst noch das ;nach dem Befehl.

if [[ -n $(find /var/log/crashes -name app-\*\.log -mmin -5) ]]; then

Dies kann möglicherweise ineffizient sein: Wenn viele Dateien vorhanden sind, durchsucht /var/log/crashesfind sie alle. Sie sollten find stoppen lassen, sobald es eine Übereinstimmung findet, oder kurz danach. Verwenden Sie bei GNU find (nicht eingebettetes Linux, Cygwin) das -quitprimäre.

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print -quit)" ]; then

Bei anderen Systemen muss die Verbindung hergestellt findwerden head, um mindestens kurz nach dem ersten Spiel zu beenden (der Fund stirbt an einer kaputten Leitung).

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print | head -n 1)" ]; then

(Sie können verwenden, head -c 1wenn Ihr headBefehl dies unterstützt.)


Alternativ können Sie auch zsh verwenden.

crash_files=(/var/log/crashes/**/app-*.log(mm-5[1]))
if (($#crash_files)); then
Gilles 'SO - hör auf böse zu sein'
quelle
1

Das sollte funktionieren

if find /var/log/crashes -name 'app-\*\.log' -mmin -5 | read
then
  service myapp restart
fi
Steven Penny
quelle
0
find /var/log/crashes -name app-\*\.log -mmin -5 -exec service myapp restart ';' -quit

ist hier eine passende Lösung.

-exec service myapp restart ';'ruft findden Befehl auf, den Sie direkt ausführen möchten, anstatt dass die Shell etwas interpretieren muss.

-quitführt finddazu, dass der Befehl nach der Verarbeitung des Befehls beendet wird, wodurch verhindert wird, dass der Befehl erneut ausgeführt wird, wenn mehrere Dateien vorhanden sind, die den Kriterien entsprechen.

Jules
quelle