Variable mit Anführungszeichen "$ ()"

12

Ich habe dieses Skript geschrieben:

#!/bin/bash
while [ true ] 
do
    currentoutput="$(lsusb)"
    if [ "$currentoutput" != "$lastoutput" ]
    then
        echo "" date and Time >> test.log
        date +%x_r >> test.log
        lastoutput="$(lsusb)"
        lsusb >> test.log
    fi
    sleep 5
done

Ich bin ein Neuling, der versucht, schnell zu lernen, und ich habe eine Frage zu den Anführungszeichen der Variablen .

Setzen Sie eine Variable zwischen $ (), ich bekomme es, aber warum werden die Anführungszeichen auch in der ifAnweisung benötigt? Soll ein verschachtelter Befehl ausgeführt werden?

Shankhara
quelle
5
Beachten Sie, dass while [ true ]dies eine Endlosschleife erzeugt, aber möglicherweise nicht aus dem Grund, den Sie glauben, dass dies der Fall ist. while [ false ] Auch erzeugt eine Endlosschleife, weil mit einem einzigen Argumente, [ ... ]gelingt es , wenn das Argument eine nicht leere Zeichenfolge ist. while truewird tatsächlich einen Befehl namens ausführentrue (der immer erfolgreich ist).
Chepner
4
Sie setzen keine Variable hinein $(). Sie haben einen Befehl eingegeben $().
Wildcard

Antworten:

22

Anführungszeichen verhindern "Wortteilung". Das heißt: Unterteilen von Variablen in mehrere Elemente mit Leerzeichen (oder genauer gesagt mit Leerzeichen, Tabulatoren und Zeilenumbrüchen, wie im Wert der Standard- $IFSShell-Variablen definiert).

Beispielsweise,

$ var="one two"
$ howmany(){ echo $#;  }
$ howmany $var
2
$ howmany "$var"
1

Hier definieren wir die howmanyFunktion, mit der wir nur wissen, wie viele Positionsparameter angegeben sind. Wie Sie sehen, werden zwei Elemente an die Variable übergeben, und mit den Anführungszeichen wird der Text in der Variablen als eine Einheit behandelt.

Dies ist wichtig für die korrekte Weitergabe von Informationen. Wenn die Variable beispielsweise den Pfad zur Datei enthält und der Dateiname an einer beliebigen Stelle im Pfad Leerzeichen enthält, schlägt der auszuführende Befehl möglicherweise fehl oder liefert ungenaue Ergebnisse. Wenn wir versuchen würden, eine Datei mit der $varVariablen touch $varzu erstellen , würden wir zwei Dateien erstellen, aber touch "$var"nur eine.

Gleiches gilt für deinen [ "$currentoutput" != "$lastoutput" ]Teil. Dieser spezielle Test führt einen Vergleich mit zwei Zeichenfolgen durch. Wenn der Test ausgeführt wird, muss der [Befehl drei Argumente anzeigen - eine Textzeichenfolge, den !=Operator und eine andere Textzeichenfolge. Wenn Sie doppelte Anführungszeichen verwenden, wird die Wortteilung verhindert, und der [Befehl erkennt genau diese drei Argumente. Was passiert nun, wenn Variablen nicht in Anführungszeichen gesetzt sind?

$ var="hello world"
$ foo="hi world"
$ [ $var != $foo ]
bash: [: too many arguments
$ 

Hier tritt Wort Spaltung und stattdessen [sieht zwei Saiten hellound worldgefolgt von !=, gefolgt von zwei anderen Saiten hi world. Entscheidend ist, dass der Inhalt von Variablen ohne doppelte Anführungszeichen als separate Einheiten und nicht als ein ganzes Element verstanden wird.

Das Zuweisen der Befehlsersetzung erfordert keine doppelten Anführungszeichen wie in

var=$( df )

wo Sie die dfAusgabe des Befehls gespeichert haben var. Es ist jedoch eine gute Angewohnheit, Variablen und Befehlsersetzungen immer in doppelte Anführungszeichen zu setzen, es sei $(...)denn, Sie möchten, dass die Ausgabe als separate Elemente behandelt wird.


Nebenbei bemerkt, die

while [ true ]

Teil kann sein

while true

[ist ein Befehl, der seine Argumente auswertet und [ whatever ]immer wahr ist, unabhängig davon, was sich darin befindet. Im Gegensatz dazu while truewird der Befehl verwendet, trueder immer den Status "Erfolgreich beendet" zurückgibt (und genau das whilebenötigt die Schleife). Der Unterschied ist ein bisschen mehr Klarheit und weniger Tests durchgeführt. Alternativ könnten Sie auch :anstelle von verwendentrue

Die doppelten Anführungszeichen zum echo "" date and TimeTeil könnten wahrscheinlich entfernt werden. Sie fügen lediglich eine leere Zeichenfolge ein und fügen der Ausgabe zusätzlichen Speicherplatz hinzu. Wenn Sie dies wünschen, können Sie sie dort aufbewahren, aber in diesem Fall gibt es keinen bestimmten funktionalen Wert.

lsusb >> test.log

Dieser Teil könnte wahrscheinlich durch ersetzt werden echo "$currentoutput" >> test.log. Es gibt keinen Grund, lsusberneut zu starten , nachdem es bereits in ausgeführt wurde currentoutput=$(lsusb). In Fällen, in denen nachgestellte Zeilenumbrüche in der Ausgabe beibehalten werden müssen, kann der Wert durch mehrmaliges Ausführen eines Befehls angezeigt werden, dies ist jedoch lsusbnicht erforderlich. Je weniger externe Befehle Sie aufrufen, desto besser, da jeder Aufruf eines nicht integrierten Befehls Kosten für CPU, Speichernutzung und Ausführungszeit verursacht (obwohl die Befehle wahrscheinlich aus dem Speicher vorgeladen sind).


Siehe auch:

Sergiy Kolodyazhnyy
quelle
1
Tolle Antwort, wenn Sie es nicht schon getan haben, sollten Sie ein Buch darüber schreiben bash. Aber im letzten Teil sollte es nicht echo "$currentoutput" >> test.log besser sein printf '%s\n' "$currentoutput" >> test.log ?
pLumo
2
@RoVo Ja, printfsollte aus Gründen der Portabilität bevorzugt werden. Da wir hier bash-spezifisches Skript verwenden, kann es zur Verwendung entschuldigt werden echo. Aber Ihre Beobachtung ist sehr richtig
Sergiy Kolodyazhnyy
1
@SergiyKolodyazhnyy, ... Ich bin mir nicht sicher, ob dies echoabsolut zuverlässig ist, auch wenn bekannt ist, dass die Shell bash ist (und der Wert von xpg_echound posixFlags bekannt ist). Wenn Ihr Wert -noder sein könnte -e, kann er verschwinden, anstatt gedruckt zu werden.
Charles Duffy
4

In currentoutput="$(lsusb)"lsusb keine Variable ist, ist es ein Befehl. Diese Anweisung führt den lsusbBefehl aus und weist die Ausgabe der currentoutputVariablen zu.

Ältere Syntax dafür war

currentoutput=`lsusb`

Sie finden es in vielen Beispielen und Skripten

Um den anderen Teil Ihrer Frage zu beantworten, if [ ]wird ifin bash lediglich die Syntax für definiert. Weitere Informationen finden Sie unter https://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_01.html

marosg
quelle
3
Ich denke, es ist wichtig zu sagen, dass dies [ ]tatsächlich der testBefehl ist. Sie können ifAnweisungen auch mit anderen Befehlen verwenden, da sie auf einem 0- oder Nicht-Null-Test ihres Exit-Codes basieren.
Arronical
... in der Tat ist es viel besser zu laufen if grep -qe "somestring" fileals zu laufen grep -qe "somestring" file; if [ $? = 0 ]; then ..., daher ist die Behauptung, if [ ...die Teil der Definition von ifSyntax ist, nicht nur irreführend, sondern führt zu schlechten Praktiken.
Charles Duffy
4

Der folgende Befehl führt den externen Befehl aus commandund gibt seine Ausgabe zurück.

"$(command)"

Ohne die Klammern würde dies nach einer Variablen suchen, anstatt einen Befehl auszuführen:

"$variable"

Der Unterschied zwischen $variableund "$variable"wird relevant, wenn $variableLeerzeichen enthalten sind. Bei der Verwendung "$variable"wird der gesamte Variableninhalt in eine einzelne Zeichenfolge eingefügt, auch wenn der Inhalt Leerzeichen enthält. Bei der Verwendung kann $variableder Inhalt der Variablen zu einer Argumentliste mit mehreren Argumenten erweitert werden.

thomasrutter
quelle
HI @ thomasrutter, es tut mir leid, ich meinte Anführungszeichen ... Ich bearbeite meinen Kommentar jetzt!
Shankhara
0

Um dagegen zu sein, empfiehlt bash, das [[ ... ]]over-the- [ ... ]Konstrukt zu verwenden, um zu vermeiden, dass Variablen im Test in Anführungszeichen gesetzt werden müssen, und damit die damit verbundenen Probleme der Wortteilung, auf die die anderen hingewiesen haben.

[wird in bash bereitgestellt, um POSIX-Kompatibilität mit Skripten zu gewährleisten, die unter #!/bin/shbash ausgeführt oder nach bash portiert werden sollen. Sie sollten dies größtenteils vermeiden [[.

z.B

# With [ - quotes are needed

$ foo='one two'; bar='one two'; [ $foo = $bar ] && echo "they're equal"
-bash: [: too many arguments

$ foo='one two'; bar='one two'; [ "$foo" = "$bar" ] && echo "they're equal"                                                                                                              
they're equal

# versus [[ - quotes not needed

$ foo='one two'; bar='one two'; [[ $foo = $bar ]] && echo "they're equal"
they're equal
shalomb
quelle
bashgibt keine solche Empfehlung ab; Es bietet, [[ ... ]] was bequemer sein kann und einige Funktionen hat, [ ... ]die nicht, aber es gibt kein Problem mit der [ ... ] richtigen Verwendung .
Chepner
@chepner - Vielleicht sollte ich hier klarer werden. Sicher, es ist keine explizite Empfehlung in der Bash-Manpage, aber gegeben [[tut alles [und noch mehr, und immer noch stolpert das viele Mal die [Leute und versucht dann, Funktionen von [[back to nachzurüsten, um [nur zu versagen und Fehler zu hinterlassen Community-Empfehlungen in Bezug auf die meisten relevanten Bash-Style-Guides raten dazu, diese niemals zu verwenden[ .
Shalomb