Ich erstelle eine Datei mit tabulatorgetrennten Feldern.
echo foo$'\t'bar$'\t'baz$'\n'foo$'\t'bar$'\t'baz > input
Ich habe das folgende Skript benannt zsh.sh
#!/usr/bin/env zsh
while read line; do
<<<$line cut -f 2
done < "$1"
Ich teste es.
$ ./zsh.sh input
bar
bar
Das funktioniert gut. Wenn ich jedoch die erste Zeile ändere, die bash
stattdessen aufgerufen wird, schlägt dies fehl.
$ ./bash.sh input
foo bar baz
foo bar baz
Warum scheitert bash
und funktioniert das zsh
?
Zusätzliche Fehlerbehebung
- Die Verwendung direkter Pfade im Shebang anstelle des
env
gleichen Verhaltens. - Das Piping mit
echo
anstelle des Here-Strings<<<$line
erzeugt ebenfalls das gleiche Verhalten. dhecho $line | cut -f 2
. - Verwenden
awk
stattcut
Arbeiten für beide Shells. dh<<<$line awk '{print $2}'
.
bash
zsh
quoting
whitespace
here-string
Sparhawk
quelle
quelle
echo -e 'foo\tbar\tbaz\n...'
,echo $'foo\tbar\tbaz\n...'
, oderprintf 'foo\tbar\tbaz\n...\n'
oder Variationen davon. Sie müssen nicht jeden Tab oder jede neue Zeile einzeln umbrechen.Antworten:
Was passiert ist, dass
bash
die Tabulatoren durch Leerzeichen ersetzt werden. Sie können dieses Problem vermeiden, indem Sie"$line"
stattdessen sagen oder Leerzeichen explizit ausschneiden.quelle
\t
und durch ein Leerzeichen ersetzt?<<< $line
,bash
teilt, aber nicht glob. Es gibt keinen Grund, warum es sich hier spalten würde, wenn man<<<
ein einziges Wort erwartet. Es teilt sich und verbindet sich dann in diesem Fall, was wenig Sinn macht und gegen alle anderen Shells-Implementierungen ist, die<<<
vorher oder nachher unterstützt habenbash
. IMO ist es ein Fehler.Das liegt daran, dass in
<<< $line
,bash
Wortaufteilung (wenn auch nicht Globbing) aktiviert wird,$line
da es dort nicht zitiert wird, und dann die resultierenden Wörter mit dem Leerzeichen verknüpft wird (und dies in eine temporäre Datei, gefolgt von einem Zeilenumbruchzeichen, einfügt und dies zum Standard machtcut
).tab
zufällig im Standardwert von$IFS
:Die Lösung
bash
besteht darin, die Variable in Anführungszeichen zu setzen.Beachten Sie, dass dies die einzige Shell ist, die dies tut.
zsh
(woher<<<
kommt, inspiriert vom Unix-Port vonrc
)ksh93
,mksh
undyash
die auch unterstützen,<<<
tun es nicht.Wenn es um die Arrays kommt,
mksh
,yash
undzsh
kommt auf dem ersten Zeichen$IFS
,bash
undksh93
auf Platz.Es gibt einen Unterschied zwischen
zsh
/yash
undmksh
(mindestens Version R52), wenn$IFS
es leer ist:Das Verhalten ist über Shells hinweg konsistenter, wenn Sie es verwenden
"${a[*]}"
(außer dass esmksh
immer noch einen Fehler gibt, wenn$IFS
es leer ist).Dies
echo $line | ...
ist jedoch der übliche Split + Glob-Operator in allen Bourne-ähnlichen Shellszsh
(und die damit verbundenen üblichen Problemeecho
).quelle
Das Problem ist, dass Sie nicht zitieren
$line
. Ändern Sie zur Untersuchung die beiden Skripte so, dass sie einfach gedruckt werden$line
:und
Vergleichen Sie nun ihre Ausgabe:
Wie Sie sehen können
$line
, werden die Registerkarten von bash nicht richtig interpretiert , da Sie nicht zitieren . Zsh scheint damit besser umzugehen. Wird jetztcut
standardmäßig\t
als Feldtrennzeichen verwendet. Da Ihrbash
Skript die Registerkarten frisst (aufgrund des Operators split + glob), wird dahercut
nur ein Feld angezeigt und es wird entsprechend gehandelt . Was Sie wirklich laufen, ist:Geben Sie Ihre Variable an, damit Ihr Skript in beiden Shells wie erwartet funktioniert:
Dann erzeugen beide die gleiche Ausgabe:
quelle
bash.sh
Wie bereits beantwortet, besteht eine portablere Möglichkeit, eine Variable zu verwenden, darin, sie in Anführungszeichen zu setzen:
Es gibt einen Unterschied in der Implementierung in Bash, mit der Zeile:
Dies ist das Ergebnis der meisten Muscheln:
Nur Bash teilt die Variable rechts von,
<<<
wenn sie nicht in Anführungszeichen steht.Dies wurde jedoch in Bash-Version 4.4 korrigiert. Dies
bedeutet, dass der Wert von
$IFS
das Ergebnis von beeinflusst<<<
.Mit der Zeile:
Alle Shells verwenden das erste Zeichen von IFS, um Werte zu verbinden.
Mit
"${l[@]}"
wird ein Leerzeichen benötigt, um die verschiedenen Argumente zu trennen, aber einige Shells verwenden den Wert von IFS (Ist das richtig?).Bei einem Null-IFS sollten die Werte wie bei dieser Zeile verbunden werden:
Aber sowohl lksh als auch mksh tun dies nicht.
Wenn wir zu einer Liste von Argumenten wechseln:
Sowohl yash als auch zsh halten die Argumente nicht getrennt. Ist das ein Fehler?
quelle
zsh
/yash
und"${l[@]}"
in Nicht-Listen-Kontexten ist dies beabsichtigt, wo"${l[@]}"
es nur in Listen-Kontexten etwas Besonderes gibt. In Nicht-Listen-Kontexten ist keine Trennung möglich. Sie müssen die Elemente irgendwie verbinden. Das Verbinden mit dem ersten Zeichen von $ IFS ist konsistenter als das Verbinden mit einem Leerzeichen-IMO.dash
macht es auch (dash -c 'IFS=; a=$@; echo "$a"' x a b
). POSIX beabsichtigt jedoch, dieses IIRC zu ändern. Siehe diese (lange) Diskussionvar=$@
nicht spezifiziert.