Ich habe gerade eine Variable zugewiesen, aber echo $ variable zeigt etwas anderes an

103

Hier sind eine Reihe von Fällen, in denen echo $varein anderer Wert als der gerade zugewiesene angezeigt werden kann. Dies geschieht unabhängig davon, ob der zugewiesene Wert "doppelt in Anführungszeichen", "einfach in Anführungszeichen" oder nicht in Anführungszeichen gesetzt wurde.

Wie bringe ich die Shell dazu, meine Variable richtig einzustellen?

Sternchen

Die erwartete Ausgabe ist /* Foobar is free software */, aber stattdessen erhalte ich eine Liste von Dateinamen:

$ var="/* Foobar is free software */"
$ echo $var 
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...

Eckige Klammern

Der erwartete Wert ist [a-z], aber manchmal bekomme ich stattdessen einen einzelnen Buchstaben!

$ var=[a-z]
$ echo $var
c

Zeilenvorschübe (Zeilenumbrüche)

Der erwartete Wert ist eine Liste separater Zeilen, aber stattdessen befinden sich alle Werte in einer Zeile!

$ cat file
foo
bar
baz

$ var=$(cat file)
$ echo $var
foo bar baz

Mehrere Leerzeichen

Ich habe einen sorgfältig ausgerichteten Tabellenkopf erwartet, aber stattdessen verschwinden mehrere Leerzeichen oder werden zu einem zusammengefasst!

$ var="       title     |    count"
$ echo $var
title | count

Tabs

Ich habe zwei durch Tabulatoren getrennte Werte erwartet, aber stattdessen erhalte ich zwei durch Leerzeichen getrennte Werte!

$ var=$'key\tvalue'
$ echo $var
key value
dieser andere Typ
quelle
2
Danke dafür. Ich stoße oft auf die Zeilenvorschübe. Ist var=$(cat file)also in Ordnung, wird aber echo "$var"benötigt.
snd
3
Übrigens, dies ist auch BashPitfalls # 14: mywiki.wooledge.org/BashPitfalls#echo_.24foo
Charles Duffy

Antworten:

139

In allen oben genannten Fällen ist die Variable korrekt eingestellt, aber nicht richtig gelesen! Der richtige Weg ist, doppelte doppelte Anführungszeichen zu verwenden, wenn auf Folgendes verwiesen wird :

echo "$var"

Dies ergibt den erwarteten Wert in allen angegebenen Beispielen. Zitieren Sie immer variable Referenzen!


Warum?

Wenn eine Variable nicht in Anführungszeichen gesetzt ist , wird Folgendes ausgeführt :

  1. Führen Sie eine Feldaufteilung durch , bei der der Wert im Leerzeichen (standardmäßig) in mehrere Wörter aufgeteilt wird:

    Vor: /* Foobar is free software */

    Nachher: /*, Foobar, is, free, software,*/

  2. Jedes dieser Wörter wird einer Pfadnamenerweiterung unterzogen , bei der Muster in übereinstimmende Dateien erweitert werden:

    Vor: /*

    Nachher: /bin, /boot, /dev, /etc, /home, ...

  3. Schließlich werden alle Argumente an echo übergeben, das sie durch einzelne Leerzeichen getrennt ausgibt und gibt

    /bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/

    anstelle des Wertes der Variablen.

Wenn die Variable in Anführungszeichen gesetzt wird , wird:

  1. Ersetzen Sie seinen Wert.
  2. Es gibt keinen Schritt 2.

Aus diesem Grund sollten Sie immer alle Variablenreferenzen zitieren , es sei denn, Sie benötigen speziell eine Wortaufteilung und eine Pfadnamenerweiterung. Tools wie Shellcheck helfen Ihnen und warnen in allen oben genannten Fällen vor fehlenden Anführungszeichen.

dieser andere Typ
quelle
es funktioniert nicht immer. Ich kann ein Beispiel geben: paste.ubuntu.com/p/8RjR6CS668
recolic
1
Ja, $(..)Streifen hinter den Zeilenvorschüben. Sie können es umgehen var=$(cat file; printf x); var="${var%x}".
andere Typ
17

Vielleicht möchten Sie wissen, warum dies geschieht. Finden Sie zusammen mit der großartigen Erklärung dieses anderen Mannes eine Referenz zu Warum erstickt mein Shell-Skript an Leerzeichen oder anderen Sonderzeichen? geschrieben von Gilles in Unix & Linux :

Warum muss ich schreiben "$foo"? Was passiert ohne die Anführungszeichen?

$foobedeutet nicht "nimm den Wert der Variablen foo". Es bedeutet etwas viel komplexeres:

  • Nehmen Sie zuerst den Wert der Variablen.
  • Feldaufteilung: Behandeln Sie diesen Wert als durch Leerzeichen getrennte Liste von Feldern und erstellen Sie die resultierende Liste. Wenn beispielsweise die Variable enthält foo * bar ​das Ergebnis dieses Schritt dann die 3-Element - Liste foo, *, bar.
  • Dateinamengenerierung: Behandeln Sie jedes Feld als Globus, dh als Platzhaltermuster, und ersetzen Sie es durch die Liste der Dateinamen, die diesem Muster entsprechen. Wenn das Muster nicht mit Dateien übereinstimmt, bleibt es unverändert. In unserem Beispiel führt dies zu einer Liste foo, die gefolgt von der Liste der Dateien im aktuellen Verzeichnis und schließlich enthält bar. Wenn das aktuelle Verzeichnis leer ist, ist das Ergebnis foo, *, bar.

Beachten Sie, dass das Ergebnis eine Liste von Zeichenfolgen ist. In der Shell-Syntax gibt es zwei Kontexte: Listenkontext und Zeichenfolgenkontext. Feldaufteilung und Dateinamengenerierung erfolgen nur im Listenkontext, dies ist jedoch meistens der Fall. Doppelte Anführungszeichen begrenzen einen Zeichenfolgenkontext: Die gesamte Zeichenfolge mit doppelten Anführungszeichen ist eine einzelne Zeichenfolge, die nicht geteilt werden darf. (Ausnahme: Das "$@"Erweitern auf die Liste der Positionsparameter "$@"entspricht z. B. "$1" "$2" "$3"drei Positionsparametern. Siehe Was ist der Unterschied zwischen $ * und $ @? )

Das gleiche gilt für die Befehlssubstitution mit $(foo)oder mit `foo`. Nebenbei bemerkt, verwenden Sie nicht `foo`: Die Anführungsregeln sind seltsam und nicht portabel, und alle modernen Shells unterstützen, $(foo)was absolut gleichwertig ist, außer dass es intuitive Anführungsregeln gibt.

Die Ausgabe der arithmetischen Substitution wird ebenfalls erweitert, dies ist jedoch normalerweise kein Problem, da sie nur nicht erweiterbare Zeichen enthält (vorausgesetzt, sie IFSenthält keine Ziffern oder -).

Siehe Wann ist eine doppelte Anführungszeichen erforderlich? Weitere Informationen zu den Fällen, in denen Sie die Anführungszeichen weglassen können.

Denken Sie daran, immer doppelte Anführungszeichen für Variablen- und Befehlsersetzungen zu verwenden, es sei denn, Sie möchten, dass all dieses Rigmarol passiert. Seien Sie vorsichtig: Das Weglassen der Anführungszeichen kann nicht nur zu Fehlern, sondern auch zu Sicherheitslücken führen .

fedorqui 'SO hör auf zu schaden'
quelle
6

Benutzer doppeltes Anführungszeichen, um den genauen Wert zu erhalten. so was:

echo "${var}"

und es wird Ihren Wert korrekt lesen.

verschwundenzhou
quelle
Funktioniert .. Danke
Tshilidzi Mudau
6

Neben anderen Problemen , verursacht durch Fehler, zu zitieren -nund -ekann verbraucht werden echoals Argumente. (Nur Ersteres ist gemäß der POSIX-Spezifikation für legal echo, aber mehrere gängige Implementierungen verstoßen gegen die Spezifikation und verbrauchen -eauch).

Um dies zu vermeiden, verwenden Sie printfanstelle von, echowenn Details wichtig sind.

So:

$ vars="-e -n -a"
$ echo $vars      # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a

Durch korrektes Zitieren werden Sie jedoch nicht immer gerettet, wenn Sie Folgendes verwenden echo:

$ vars="-n"
$ echo $vars
$ ## not even an empty line was printed

... während es wird Sie mit speichern printf:

$ vars="-n"
$ printf '%s\n' "$vars"
-n
Charles Duffy
quelle
Ja, wir brauchen dafür ein gutes Engagement! Ich bin damit einverstanden, dass dies zum Titel der Frage passt, aber ich denke nicht, dass es die Sichtbarkeit bekommt, die es hier verdient. Wie wäre es mit einer neuen Frage à la "Warum wird mein -e/ -n/ Backslash nicht angezeigt?" Wir können von hier aus gegebenenfalls Links hinzufügen.
andere Typ
Meinen Sie verbrauchen -nauch ?
PesaDer
1
@PesaThe, nein, ich meinte -e. Der Standard für echogibt die Ausgabe nicht an, wenn das erste Argument lautet -n, sodass in diesem Fall alle möglichen Ausgaben zulässig sind. es gibt keine solche Bestimmung für -e.
Charles Duffy
Oh ... ich kann nicht lesen. Machen wir mein Englisch dafür verantwortlich. Danke für die Erklärung.
PesaDer
2

echo $varDie Ausgabe hängt stark vom Wert der IFSVariablen ab. Standardmäßig enthält es Leerzeichen, Tabulatoren und Zeilenumbrüche:

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
 ^I$

Dies bedeutet, dass die Shell bei der Feldaufteilung (oder Wortaufteilung) alle diese Zeichen als Worttrennzeichen verwendet. Dies geschieht, wenn auf eine Variable ohne doppelte Anführungszeichen verwiesen wird, um sie wiederzugeben ( $var), und daher die erwartete Ausgabe geändert wird.

Eine Möglichkeit, die Wortteilung zu verhindern (neben der Verwendung von doppelten Anführungszeichen), besteht darin, IFSden Wert auf Null zu setzen . Siehe http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 :

Wenn der Wert von IFS null ist, darf keine Feldaufteilung durchgeführt werden.

Das Setzen auf Null bedeutet das Setzen auf einen leeren Wert:

IFS=

Prüfung:

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
 ^I$
[ks@localhost ~]$ var=$'key\nvalue'
[ks@localhost ~]$ echo $var
key value
[ks@localhost ~]$ IFS=
[ks@localhost ~]$ echo $var
key
value
[ks@localhost ~]$ 
ks1322
quelle
2
Sie müssten auch set -fverhindern, dass Globbing
dieser andere Typ
@thatotherguy, ist es wirklich notwendig für dein 1. Beispiel mit Pfaderweiterung? Mit IFSauf null gesetzt, echo $varwird erweitert echo '/* Foobar is free software */'und Expansionspfad ist nicht in einzelnen Strings in Anführungszeichen ausgeführt.
ks1322
1
Ja. Wenn Sie mkdir "/this thing called Foobar is free software etc/"sehen, dass es sich immer noch ausdehnt. Für das [a-z]Beispiel ist es offensichtlich praktischer .
andere Typ
Ich sehe, das macht zum [a-z]Beispiel Sinn .
ks1322
2

Die Antwort von ks1322 hat mir geholfen, das Problem bei der Verwendung zu identifizieren docker-compose exec:

Wenn Sie das -TFlag weglassen , docker-compose execfügen Sie ein Sonderzeichen hinzu, das die Ausgabe unterbricht. Wir sehen bstattdessen 1b:

$ test=$(/usr/local/bin/docker-compose exec db bash -c "echo 1")
$ echo "${test}b"
b
echo "${test}" | cat -vte
1^M$

Funktioniert mit -Tflag docker-compose execwie erwartet:

$ test=$(/usr/local/bin/docker-compose exec -T db bash -c "echo 1")
$ echo "${test}b"
1b
AL
quelle
-2

Zusätzlich zum Anführungszeichen der Variablen könnte man auch die Ausgabe der Variablen übersetzen trund Leerzeichen in Zeilenumbrüche konvertieren.

$ echo $var | tr " " "\n"
foo
bar
baz

Dies ist zwar etwas komplizierter, bietet jedoch mehr Vielfalt bei der Ausgabe, da Sie jedes Zeichen als Trennzeichen zwischen Arrayvariablen einsetzen können.

Alek
quelle
2
Dies ersetzt jedoch alle Leerzeichen durch Zeilenumbrüche. Beim Zitieren bleiben die vorhandenen Zeilenumbrüche und Leerzeichen erhalten.
user000001
Richtig, ja. Ich nehme an, es hängt davon ab, was sich in der Variablen befindet. Ich verwende eigentlich trumgekehrt, um Arrays aus Textdateien zu erstellen.
Alek
3
Es ist keine gute Programmierung, ein Problem zu erstellen, indem die Variable nicht richtig zitiert und dann mit einem zusätzlichen Prozess umgangen wird.
Tripleee
@Alek, ... ähm, was? Es ist nicht trerforderlich, ein Array ordnungsgemäß / korrekt aus einer Textdatei zu erstellen. Sie können das gewünschte Trennzeichen angeben, indem Sie IFS festlegen. Zum Beispiel: Funktioniert den IFS=$'\n' read -r -d '' -a arrayname < <(cat file.txt && printf '\0')ganzen Weg zurück durch Bash 3.2 (die älteste Version in großer Verbreitung) und setzt den Exit-Status korrekt auf false, wenn Ihr Fehler cataufgetreten ist. Und wenn man wollte, sagen wir, Tabs statt Zeilenumbrüche, würden Sie einfach ersetzen die $'\n'mit $'\t'.
Charles Duffy
1
@Alek, ... wenn Sie so etwas tun arrayname=( $( cat file | tr '\n' ' ' ) ), dann ist das auf mehreren Ebenen kaputt: Es überträgt Ihre Ergebnisse (so *wird eine Liste von Dateien im aktuellen Verzeichnis), und es würde genauso gut funktionieren ohnetr ( oder das cat, was man betrifft, man könnte es einfach benutzen arrayname=$( $(<file) )und es würde auf die gleiche Weise kaputt gehen, aber weniger ineffizient).
Charles Duffy