Testen Sie, ob ein Befehl eine leere Zeichenfolge ausgibt

237

Wie kann ich testen, ob ein Befehl eine leere Zeichenfolge ausgibt?

barp
quelle
7
Ein Befehl gibt keine Zeichenfolge zurück (er gibt einen kleinen ganzzahligen Exit-Code zurück, normalerweise 0 für Erfolg und 1 oder 2 bei Fehler), kann jedoch einige Daten ausgeben , um eine Zeichenfolge einzugeben.
Basile Starynkevitch
@BasileStarynkevitch das ist das, was ich brauche. Ich weiß, Befehl gibt Exit-Code zurück. Aber meine Befehle Exit-Code ist immer 0. Also muss ich die Ausgabe steuern
Barp
1
Sie sollten einige Bash Scripting Tutorial tldp.org/LDP/abs/html
Basile Starynkevitch
Joey Hess hat dafür ein Dienstprogramm ifne. joeyh.name/code/moreutils
Tripleee

Antworten:

309

Zuvor wurde gefragt, wie überprüft werden soll, ob sich Dateien in einem Verzeichnis befinden. Der folgende Code erreicht dies, aber siehe Antwort von rsp für eine bessere Lösung.


Ausgabe leeren

Befehle nicht zurückgeben Werte - sie geben sie. Sie können diese Ausgabe mithilfe der Befehlssubstitution erfassen . zB $(ls -A). Sie können in Bash wie folgt auf eine nicht leere Zeichenfolge testen:

if [[ $(ls -A) ]]; then
    echo "there are files"
else
    echo "no files found"
fi

Beachten Sie, dass ich -Aeher als verwendet habe -a, da die symbolischen Verzeichniseinträge current ( .) und parent ( ..) weggelassen werden.

Hinweis: Wie in den Kommentaren erwähnt, werden beim Ersetzen von Befehlen keine nachgestellten Zeilenumbrüche erfasst . Wenn der Befehl nur Zeilenumbrüche ausgibt , erfasst die Ersetzung daher nichts und der Test gibt false zurück. Dies ist im obigen Beispiel zwar sehr unwahrscheinlich, aber möglich, da ein einzelner Zeilenumbruch ein gültiger Dateiname ist! Weitere Informationen in dieser Antwort .


Code beenden

Wenn Sie überprüfen möchten, ob der Befehl erfolgreich ausgeführt wurde, können Sie überprüfen $?, welcher den Exit-Code des letzten Befehls enthält (Null für Erfolg, Nicht-Null für Fehler). Beispielsweise:

files=$(ls -A)
if [[ $? != 0 ]]; then
    echo "Command failed."
elif [[ $files ]]; then
    echo "Files found."
else
    echo "No files found."
fi

Mehr Infos hier .

Will Vousden
quelle
4
Sie können weglassen -n , so wird es nur seinif [[ $(ls -A) ]]; then
Benutzer
6
Diese Methode gibt false für Befehle aus, die nur Zeilenumbrüche ausgeben.
Ciro Santilli 18 冠状 病 六四 事件 18
89

TL; DR

if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then ...; fi

Vielen Dank an netj für einen Vorschlag zur Verbesserung meines Originals:
if [[ $(ls -A | wc -c) -ne 0 ]]; then ...; fi


Dies ist eine alte Frage, aber ich sehe mindestens zwei Dinge, die verbessert oder zumindest geklärt werden müssen.

Erstes Problem

Das erste Problem, das ich sehe, ist, dass die meisten der hier bereitgestellten Beispiele einfach nicht funktionieren . Sie verwenden die Befehle ls -alund ls -Al- beide geben nicht leere Zeichenfolgen in leeren Verzeichnissen aus. In diesen Beispielen wird immer angegeben , dass Dateien vorhanden sind, auch wenn keine vorhanden sind .

Aus diesem Grund sollten Sie nur ls -A- Warum sollte jemand den -lSchalter verwenden wollen, was "ein langes Listenformat verwenden" bedeutet, wenn Sie nur testen möchten, ob eine Ausgabe vorhanden ist oder nicht?

Die meisten Antworten hier sind also einfach falsch.

Zweites Problem

Das zweite Problem ist , dass , während einige Antworten gut funktionieren (diejenigen , die nicht verwendet werden ls -aloder ls -Alaber ls -Astattdessen) sie alle etwas tun:

  1. Führen Sie einen Befehl aus
  2. Puffern Sie die gesamte Ausgabe im RAM
  3. Konvertieren Sie die Ausgabe in eine große einzeilige Zeichenfolge
  4. Vergleichen Sie diese Zeichenfolge mit einer leeren Zeichenfolge

Was ich stattdessen vorschlagen würde, wäre:

  1. Führen Sie einen Befehl aus
  2. Zählen Sie die Zeichen in der Ausgabe, ohne sie zu speichern
    • oder noch besser - zähle die Anzahl von maximal 1 Zeichen using head -c1
      (danke an netj für die Veröffentlichung dieser Idee in den Kommentaren unten)
  3. Vergleiche diese Zahl mit Null

Also zum Beispiel anstelle von:

if [[ $(ls -A) ]]

Ich würde ... benutzen:

if [[ $(ls -A | wc -c) -ne 0 ]]
# or:
if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]

Anstatt:

if [ -z "$(ls -lA)" ]

Ich würde ... benutzen:

if [ $(ls -lA | wc -c) -eq 0 ]
# or:
if [ $(ls -lA | head -c1 | wc -c) -eq 0 ]

und so weiter.

Bei kleinen Ausgängen ist dies möglicherweise kein Problem, bei größeren Ausgängen kann der Unterschied jedoch erheblich sein:

$ time [ -z "$(seq 1 10000000)" ]

real    0m2.703s
user    0m2.485s
sys 0m0.347s

Vergleichen Sie es mit:

$ time [ $(seq 1 10000000 | wc -c) -eq 0 ]

real    0m0.128s
user    0m0.081s
sys 0m0.105s

Und noch besser:

$ time [ $(seq 1 10000000 | head -c1 | wc -c) -eq 0 ]

real    0m0.004s
user    0m0.000s
sys 0m0.007s

Vollständiges Beispiel

Aktualisiertes Beispiel aus der Antwort von Will Vousden:

if [[ $(ls -A | wc -c) -ne 0 ]]; then
    echo "there are files"
else
    echo "no files found"
fi

Nach Vorschlägen von netj erneut aktualisiert :

if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then
    echo "there are files"
else
    echo "no files found"
fi

Zusätzliches Update von jakeonfire :

grepwird mit einem Fehler beendet, wenn keine Übereinstimmung vorliegt. Wir können dies nutzen, um die Syntax etwas zu vereinfachen:

if ls -A | head -c1 | grep -E '.'; then
    echo "there are files"
fi

if ! ls -A | head -c1 | grep -E '.'; then
    echo "no files found"
fi

Leerzeichen verwerfen

Wenn der zu testende Befehl ein Leerzeichen ausgeben könnte, das Sie als leere Zeichenfolge behandeln möchten, dann anstelle von:

| wc -c

Du könntest benutzen:

| tr -d ' \n\r\t ' | wc -c

oder mit head -c1:

| tr -d ' \n\r\t ' | head -c1 | wc -c

oder etwas ähnliches.

Zusammenfassung

  1. Verwenden Sie zunächst einen funktionierenden Befehl .

  2. Vermeiden Sie zweitens unnötiges Speichern im RAM und die Verarbeitung potenziell großer Datenmengen.

In der Antwort wurde nicht angegeben, dass die Ausgabe immer klein ist, daher muss auch die Möglichkeit einer großen Ausgabe berücksichtigt werden.

rsp
quelle
3
[[ $(... | head -c | wc -c) -gt 0 ]]oder [[ -n $(... | head -c1) ]]sind besser, weil wc -cdie gesamte Ausgabe des Befehls verbrauchen müsste.
netj
@netj Das ist eine sehr gute Idee. Siehe meine aktualisierte Antwort - Ich habe Ihren Vorschlag hinzugefügt. Vielen Dank!
rsp
Irgendwelche Ideen, wie man die Ausgabe in den Griff bekommt?
fahrradflucht
Ich denke, das Original (ohne Kopf) ist im allgemeinen Fall besser. Es ist nicht immer eine gute Idee, den Befehl zu beenden, sobald er mit dem Schreiben der Ausgabe beginnt!
stk
@stk Die meisten Programme werden nach dem Empfang eines Kill-Signals sicher beendet.
Cambunctious
40
if [ -z "$(ls -lA)" ]; then
  echo "no files found"
else
  echo "There are files"
fi

Dadurch wird der Befehl ausgeführt und geprüft, ob die zurückgegebene Ausgabe (Zeichenfolge) eine Länge von Null hat. Möglicherweise möchten Sie die 'Test'-Handbuchseiten auf andere Flags überprüfen .

Verwenden Sie das "" um das zu prüfende Argument, da sonst leere Ergebnisse zu einem Syntaxfehler führen, da kein zweites zu prüfendes Argument angegeben wird!

Hinweis: Das ls -lakehrt immer zurück .und ..daher funktioniert die Verwendung nicht, siehe ls Handbuchseiten . Obwohl dies bequem und einfach erscheinen mag, wird es wahrscheinlich leicht brechen. Das Schreiben eines kleinen Skripts / einer kleinen Anwendung, die je nach Ergebnis 0 oder 1 zurückgibt, ist viel zuverlässiger!

Veger
quelle
Sie könnten es vorziehen, $(anstelle von Backquote zu verwenden, weil $(Nest besser
Basile Starynkevitch
Sie haben Recht, es ist besser, sie immer zu verwenden, obwohl wir hier keine Verschachtelung benötigen.
Veger
23

Für diejenigen, die eine elegante, von der Bash-Version unabhängige Lösung suchen (die eigentlich in anderen modernen Shells funktionieren sollte) und für diejenigen, die gerne Einzeiler für schnelle Aufgaben verwenden. Auf geht's!

ls | grep . && echo 'files found' || echo 'files not found'

(Beachten Sie als einer der erwähnten Kommentare, ls -alund in der Tat, nur -lund -awerden alle etwas zurückgeben, so verwende ich in meiner Antwort einfachls

Alex
quelle
5
Wenn Sie grep -q ^stattdessen verwenden, werden auch Zeilenumbrüche abgeglichen, und grep druckt nichts in die Standardausgabe. Darüber hinaus wird es beendet, sobald eine Eingabe empfangen wird, anstatt auf das Ende seines Eingabestreams zu warten.
Mortehu
Sollte mit beginnen, ls -Awenn Sie
Punktdateien
13

Bash Referenzhandbuch

6.4 Bedingte Bash-Ausdrücke

-z string
     True if the length of string is zero.

-n string
string
     True if the length of string is non-zero.

Sie können die Kurzversion verwenden:

if [[ $(ls -A) ]]; then
  echo "there are files"
else
  echo "no files found"
fi
Benutzer
quelle
1
In parenthessis fehlt [-...] ein -z oder -n.
71GA
4
@ 71GA: Vielleicht ist das leicht zu übersehen, aber wenn Sie genau hinschauen, werden Sie sehen, dass dies nur stringein Synonym für ist -n string.
Benutzer
10

Wie Jon Lin kommentierte, ls -alwird immer (für .und ..) ausgegeben . Sie möchten ls -Aldiese beiden Verzeichnisse vermeiden.

Sie können beispielsweise die Ausgabe des Befehls in eine Shell-Variable einfügen:

v=$(ls -Al)

Eine ältere, nicht verschachtelbare Notation ist

v=`ls -Al`

aber ich bevorzuge die nestbare Notation $(...)

Sie können testen, ob diese Variable nicht leer ist

if [ -n "$v" ]; then
    echo there are files
else
    echo no files
fi

Und Sie könnten beide als kombinieren if [ -n "$(ls -Al)" ]; then

Basile Starynkevitch
quelle
5

Ich vermute, Sie möchten die Ausgabe des ls -alBefehls, also hätten Sie in bash so etwas wie:

LS=`ls -la`

if [ -n "$LS" ]; then
  echo "there are files"
else
  echo "no files found"
fi
Jon Lin
quelle
Dies funktioniert nicht: Der Befehl lswird nie ausgeführt. Sie prüfen nur, ob die Erweiterung der Variablen LS nicht leer ist.
gniourf_gniourf
1
@gniourf_gniourf - warum denkst du das? Die Backtick-Beschwörung ist zwar nicht so hübsch oder flexibel wie $ (), aber sie funktioniert ...
Tink
Der -aSwitch gibt niemals keinen Inhalt zurück (dh er gibt Inhalt zurück, unabhängig davon, ob Dateien vorhanden sind oder nicht), da immer das Implizite .und die ..Verzeichnisse vorhanden sind.
Wrlee
3

Hier ist eine Lösung für extremere Fälle:

if [ `command | head -c1 | wc -c` -gt 0 ]; then ...; fi

Das wird funktionieren

  • für alle Bourne Muscheln;
  • wenn die Befehlsausgabe alle Nullen ist;
  • effizient unabhängig von der Ausgabegröße;

jedoch,

  • Der Befehl oder seine Unterprozesse werden beendet, sobald etwas ausgegeben wird.
Curt
quelle
1

Alle bisher gegebenen Antworten beziehen sich auf Befehle, die eine nicht leere Zeichenfolge beenden und ausgeben.

Die meisten sind in folgenden Sinnen gebrochen:

  • Sie behandeln Befehle, die nur Zeilenumbrüche ausgeben, nicht richtig.
  • Ab Bash≥4.4 werden die meisten Standardfehler als Spam versenden, wenn der Befehl null Bytes ausgibt (da sie die Befehlssubstitution verwenden).
  • Die meisten schlürfen den gesamten Ausgabestream und warten, bis der Befehl beendet ist, bevor sie antworten. Einige Befehle werden nie beendet (versuchen Sie es z yes. B. ).

Um all diese Probleme zu beheben und die folgende Frage effizient zu beantworten,

Wie kann ich testen, ob ein Befehl eine leere Zeichenfolge ausgibt?

Sie können verwenden:

if read -n1 -d '' < <(command_here); then
    echo "Command outputs something"
else
    echo "Command doesn't output anything"
fi

Sie können auch eine Zeitüberschreitung hinzufügen, um zu testen, ob ein Befehl innerhalb einer bestimmten Zeit eine nicht leere Zeichenfolge ausgibt, indem Sie readdie -tOption 'verwenden. ZB für eine Zeitüberschreitung von 2,5 Sekunden:

if read -t2.5 -n1 -d '' < <(command_here); then
    echo "Command outputs something"
else
    echo "Command doesn't output anything"
fi

Anmerkung. Wenn Sie der Meinung sind, dass Sie feststellen müssen, ob ein Befehl eine nicht leere Zeichenfolge ausgibt, liegt höchstwahrscheinlich ein XY-Problem vor.

gniourf_gniourf
quelle
Ich denke, diese Antwort wird eher unterschätzt. Ich war Leistungstests hier einige der Lösungen 100 Iterationen für jede der drei Eingangsszenarien ( seq 1 10000000, seq 1 20, und printf""). Das einzige Matchup, das dieser Leseansatz nicht gewann, war der -z "$(command)"Ansatz für eine leere Eingabe. Selbst dann verliert es nur um etwa 10-15%. Sie scheinen ausgeglichen zu sein seq 1 10. Unabhängig davon wird der -z "$(command)"Ansatz mit zunehmender Eingabegröße so langsam, dass ich ihn nicht für Eingaben mit variabler Größe verwenden würde.
Abathur
0

Hier ist ein alternativer Ansatz, bei dem std-out und std-err eines Befehls in eine temporäre Datei geschrieben und anschließend überprüft wird, ob diese Datei leer ist. Ein Vorteil dieses Ansatzes besteht darin, dass beide Ausgänge erfasst werden und keine Unterschalen oder Rohre verwendet werden. Diese letzteren Aspekte sind wichtig, da sie die Behandlung des Bash-Exit-Trapping beeinträchtigen können (z. B. hier ).

tmpfile=$(mktemp)
some-command  &> "$tmpfile"
if [[ $? != 0 ]]; then
    echo "Command failed"
elif [[ -s "$tmpfile" ]]; then
    echo "Command generated output"
else
    echo "Command has no output"
fi
rm -f "$tmpfile"
Darren Smith
quelle
0

Manchmal möchten Sie die Ausgabe speichern, wenn sie nicht leer ist, um sie an einen anderen Befehl zu übergeben. Wenn ja, könnten Sie so etwas verwenden

list=`grep -l "MY_DESIRED_STRING" *.log `
if [ $? -eq 0 ]
then
    /bin/rm $list
fi

Auf diese Weise rmbleibt der Befehl nicht hängen, wenn die Liste leer ist.

Scott C Wilson
quelle