Wortteilung in Positionsparametern


7

Betrachten Sie das folgende Beispiel:

IFS=:
x="a   :b"   # three spaces
echo ["$x"]  # no word splitting
# [a   :b]   # as is
echo [$x]    # word splitting 
# [a    b]   # four spaces

Die Wortaufteilung identifiziert die Wörter "a "(drei Leerzeichen) und verbindet die Wörter "b", getrennt durch den Doppelpunkt, echomit einem Leerzeichen in der Mitte.
Wenn $xich jedoch den Wert von als Funktionsargument verwende, fällt es mir schwer, die Ergebnisse zu interpretieren.

args(){ echo ["$*"];}
args a   :b  # three spaces
# [a::b]

und:

args(){ echo [$*];}
args a   :b  # three spaces
# [a  b]     # two spaces

$*wird auf den Wert aller kombinierten Positionsparameter erweitert. Auch "$*"entspricht "$1c$2", wo cdas erste Zeichen von dem Wert des IFS - Variable.

args(){ echo ["$1"]["$2"]; }
args a   :b  # three spaces
# [a][:b]

und:

args(){ echo [$1][$2]; }
args a   :b  # three spaces
# [a][ b]   

Die Wortaufteilung sollte immer dann erfolgen, wenn nicht zitierte Erweiterungen vorhanden sind. Hier "$1"und $1sind gleich und in beiden Fällen wird das :Trennzeichen nicht verwendet . [$2]-> [ b]ist auch unklar.

Wahrscheinlich werden vor dem Anwenden der IFS-Aufteilung andere Tokenisierungsregeln verwendet, aber ich konnte sie nicht finden.

Antworten:


12

Die Wortaufteilung gilt nur für nicht zitierte Erweiterungen (Parametererweiterung, arithmetische Erweiterung und Befehlssubstitution) in modernen Bourne-ähnlichen Shells (in zsh, nur Befehlssubstitution, sofern Sie keinen Emulationsmodus verwenden).

Wenn Sie das tun:

args a    :b

Wortaufteilung ist überhaupt nicht beteiligt.

Es ist die Shell-Analyse, die diese tokenisiert, feststellt, dass das erste nicht eines seiner Schlüsselwörter ist, und daher ist es ein einfacher Befehl mit 3 Argumenten : args, aund :b. Der Platz macht dort keinen Unterschied. Beachten Sie, dass es sich nicht nur um Leerzeichen, sondern auch um Tabulatoren und in einigen Shells (wie yashoder bash) um Zeichen handelt, die in Ihrem Gebietsschema als leer betrachtet werden (im Fall von bashnicht den Multibyte- Zeichen ) ¹.

Auch in dem Bourne - Shell , wo einzelnen Worte aufgespalten auch nicht notierten Argumente von Befehlen unabhängig davon angewandt , ob sie das Ergebnis der Erweiterungen waren oder nicht, die getan werden würde auf der Oberseite (lange nach) den tokenising und Syntax - Analyse.

In der Bourne-Shell

IFS=i
while bib=did edit foo

Das würde das nicht analysieren als:

"wh" "le b" "b=d" "d ed" "t foo"

Aber erst als whilemit einem einfachen Befehl und das editWort (wie es ist ein Argument , aber nicht das bid=didWort , das eine Zuordnung ist) dieser einfache Befehl würde weiter aufgeteilt in edund tso , dass der edBefehl mit den drei Argumenten ed, tund fooals die ausgeführt werden würde Zustand dieser whileSchleife.

Die Wortaufteilung ist nicht Teil der Syntaxanalyse. Es ist wie ein Operator, der implizit auf Argumente (auch in forSchleifenwörtern, Arrays und mit einigen Shell das Ziel von Umleitungen und einigen anderen Kontexten ) für die Teile von ihnen angewendet wird , die nicht in Anführungszeichen stehen. Was verwirrend ist, ist, dass es implizit gemacht wird . Sie tun es nicht cmd split($x), Sie tun es cmd $xund das split()( tatsächlichglob(split()) ) ist impliziert. In zshmüssen Sie es explizit für Parametererweiterungen anfordern ( split($x)ist $=xda ( $=sieht aus wie eine Schere)).

Nun zu Ihren Beispielen:

args(){ echo ["$*"];}
args a   :b  # three spaces
# [a::b]

aund :bArgumente argsmit dem ersten Zeichen verbunden $IFSdie gibt a::b(beachten Sie, dass es eine schlechte Idee, mit [...]hier , wie es ist ein Globbing Operator).

args(){ echo [$*];}
args a   :b  # three spaces
# [a  b]     # two spaces

$*(was enthält a::b) wird aufgeteilt in adie leere Zeichenfolge und b. So ist es:

echo '[a' '' 'b]'
args(){ echo ["$1"]["$2"]; }
args a   :b  # three spaces
# [a][:b]

Kein Wunder, da es keine Wortspaltung gibt.

args(){ echo [$1][$2]; }
args a   :b  # three spaces
# [a][ b]   

Das ist wie:

 echo '[a]' '[' 'b]'

as $2( :b) würde in die leere Zeichenfolge und aufgeteilt b.

Ein Fall, in dem Sie Abweichungen zwischen den Implementierungen feststellen, ist, wenn er $IFSleer ist.

Im:

set a b
IFS=
printf '<%s>\n' $*

In einigen Muscheln (heutzutage meistens) sehen Sie

<a>
<b>

Und nicht <ab>einmal "$*"würde sich erweitern ab. Diese Schalen trennen diese aund bPositionsparameter immer noch, und dies wurde in der neuesten Version des Standards als POSIX-Anforderung festgelegt.

Wenn du. .. getan hast:

set a b
IFS=
var="$*" # note that the behaviour for var=$* is unspecified
printf '<%s>\n' $var

Sie würden sehen, <ab>dass die Information, dass aund b2 separate Argumente waren, verloren ging, wenn sie zugewiesen wurde $var.


¹ Natürlich sind es nicht nur Leerzeichen, die Wörter abgrenzen. Es gibt auch spezielle Token in der Shell-Syntax, deren Liste vom Kontext abhängt. In den meisten Kontexten |, ||, &, ;, Newline <, >, >>... abgrenzen Worte. Zum ksh93Beispiel können Sie einen Befehl ohne Leerzeichen schreiben, wie:

while({([[(:)]])})&&((1||1))do(:);uname<&2|tee>(rev)file;done

Vielen Dank. Also sollte ich Wortspaltung haben, gegeben IFS=:und x="foo :bar", in args $x. Wie wäre die Reihenfolge hier? Zum Beispiel wird nach dem Token argsund dem $xIdentifizieren des letzteren das Token erweitert und erneut markiert.
Antonio

@antonio, es gibt kein Retokensing , es gibt das Tokenisieren, das durchgeführt wird, wenn die Shell ihre Syntax analysiert, und es gibt die Wortaufteilung, einen speziellen Operator, der auf nicht zitierte Erweiterungen angewendet wird. Mit args $xist es, als ob Sie run_simple_command("args", split($x))in einer anderen Sprache geschrieben hätten (und run_simple_command(glob("args"), glob(split($x)))Globbing gilt auch). Während für die Bourne-Shell wäre es so gewesen run_simple_command(glob(split("args")), glob(split($x))). Siehe auch: Sicherheitsauswirkungen des Vergessens, eine Variable in Bash / POSIX-Shells zu zitieren
Stéphane Chazelas

@ StéphaneChazelas Ich denke , es Ungenauigkeit hier ist (zweites Beispiel): $* (which contains a::b) is split into a, the empty string and b.Warum $*ist a::b? Ich denke als nächstes: das $1= a, $2= :bund $*verarbeitet das aohne Änderungen, teilt dann das :bin zwei Teile - der linke ist ein leerer String und der rechte ist b. Also bekommen wir {a}{}{b}. Versuchen Sie es args :b. Sie erhalten auch eine leere Zeichenfolge - {}{b}.
MiniMax

@ StéphaneChazelas Ich habe diesen Code zum Testen verwendet:args(){ echo '$1 = '"$1"; echo '$2 = '"$2"; printf '{%s}' $*; echo ""; } args :b; args a :b;
MiniMax

1
@MiniMax, das läuft auf meinen leeren IFS-Fall hinaus. Ob $*in "a", "" und "b" aufgeteilt wird, weil es getrennt ist "a" und ": b" aufgeteilt in "" und "b" (wie in einigen Shells), oder weil wenn $ * zuerst in "a" verbunden ist :: b "und dann in" a "," "und" b "aufgeteilt (wie in einigen anderen Shells) führt ohnehin in allen Fällen zum gleichen Ergebnis, mit Ausnahme des leeren IFS- Falls.
Stéphane Chazelas
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.