|
kB
Supporter, Wikiteam
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 9837
|
Ich möchte in einem bash-Skript aus dem Argumentstring $* das letzte Wort abschneiden. Um das zu testen, benutze ich dieses Skript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | #! /bin/bash
X=$*
echo X=$X.
echo '${X%e}='${X%e}.
echo '"${X%e}"='"${X%e}".
echo -----
echo '*='$*.
echo '@='$@.
echo '"*"='"$*".
echo '"@="'"$@".
echo '${*%e}='${*%e}.
echo '"${*%e}"='"${*%e}".
echo '"${@%e}"='"${@%e}".
echo '"${@%e}"='"${@%e}".
|
Beispielsweise mit diesem Ergebnis: 1
2
3
4
5
6
7
8
9
10
11
12
13 | $ ~/Skripte/TEST 1 e2 ea ae 3e '4 5' e
X=1 e2 ea ae 3e 4 5 e.
${X%e}=1 e2 ea ae 3e 4 5 .
"${X%e}"=1 e2 ea ae 3e 4 5 .
-----
*=1 e2 ea ae 3e 4 5 e.
@=1 e2 ea ae 3e 4 5 e.
"*"=1 e2 ea ae 3e 4 5 e.
"@="1 e2 ea ae 3e 4 5 e.
${*%e}=1 e2 ea a 3 4 5 .
"${*%e}"=1 e2 ea a 3 4 5 .
"${@%e}"=1 e2 ea a 3 4 5 .
"${@%e}"=1 e2 ea a 3 4 5 .
|
Wenn ich den Argumentstring erst einer Variablen X zuweise und diese bearbeite, passiert genau das, was ich haben will. Nun will ich aber auf die Variable X verzichten und mit $* direkt arbeiten. Das verhält sich anders: Es wird aus jedem Wort das Ende weggeschnitten, und zwar unabhängig von der Verwendung von $@ oder $* und auch unabhängig vom Quoting. Das Verhalten würde ich beim Array $@ erwarten, aber nicht bei $*! Warum verhält sich hier $* genau wie $@, entgegen der Dokumentation? Ist das ein Bug in bash? Bearbeitet von rklm: Syntaxhighlighting
|
|
shiro
Supporter
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1303
|
Die Parameter sind ein normales Array. Wenn du die Array-Elemente einer Variablen zuweist, klappt das Löschen des letzten "e", wie du auch festgestellt hast, sonst wird jedes einzelne Element behandelt.
| $ arr=( 1 e2 ea ae 3e '4 5' e )
$ echo ${#arr[@]}: ${arr[@]%e}
7: 1 e ea ae 3e 4 5 e
$ echo ${#arr[*]}: ${arr[*]%e}
7: 1 e ea ae 3e 4 5 e
$ echo ${arr[*]%e}
1 e2 ea a 3 4 5
$ echo ${arr[@]%e}
1 e2 ea a 3 4 5
$ x=${arr[@]};echo ${x%e}
1 e2 ea ae 3e 4 5
|
Die Parameter "*" und "@" werden in diesem Kontext durchaus gleich behandelt. Sie unterscheiden sich in wenigen Eigenschaften (z.B. "word splitting" und "pathname expansion"). Da im "${}" Ausdruck eine doppelte Substitution nicht auftreten darf, muss man leider den Weg über eine Hilfsvariable machen. > Warum verhält sich hier $* genau wie $@, entgegen der Dokumentation? Ist das ein Bug in bash?
Die Anmerkung habe ich leider nicht genau genug verstanden. Wo hättest du bei den gewählten Parametern eine unterschiedliche Behandlung erwartet (gemäß man page)? Oder beziehst du dich auf die (1st char) IFS Eigenschaft von "$*"? Diese wird aber erst nach der Bearbeitung des Arrays durchgeführt.
| $ savIFS="$IFS";export IFS=","
$ cat x.sh
echo ">$*<"
echo ">$@<"
echo "${*%e}"
echo "${@%e}"
$ . x.sh 1 e2 ea ae 3e '4 5' e
>1,e2,ea,ae,3e,4 5,e<
>1 e2 ea ae 3e 4 5 e<
1,e2,ea,a,3,4 5,
1 e2 ea a 3 4 5
|
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13242
|
Am einfachsten erscheint mir, das in ein Array zu packen und per unset das letzte zu löschen: | tmp=("$@")
echo "vorher : ${#tmp[@]}: ${tmp[*]}"
unset tmp[$(( ${#tmp[@]} - 1 ))]
echo "nachher: ${#tmp[@]}: ${tmp[*]}"
|
Wenn Du das Argument-Array verändern willst, kannst Du am Ende wieder zurück kopieren: | set -- "${tmp[@]}"
unset tmp
|
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11278
|
Man kann auch den Array mit Ausnahme des letzten Elements ausgeben:
| echo "${@:1:$((${#@} - 1))}"
|
|
|
kB
Supporter, Wikiteam
(Themenstarter)
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 9837
|
shiro schrieb: […] Wo hättest du bei den gewählten Parametern eine unterschiedliche Behandlung erwartet (gemäß man page)?
Es geht um diesen Passus aus der man page von bash:
Special Parameters
The shell treats several parameters specially. These parameters may only be referenced; assignment to them is not al‐
lowed. sitional parameter expands to a separate word. In contexts where it is performed, those words are subject to fur‐
ther word splitting and pathname expansion. When the expansion occurs within double quotes, it expands to a sin‐
gle word with the value of each parameter separated by the first character of the IFS special variable. That is,
"$*" is equivalent to "$1c$2c...", where c is the first character of the value of the IFS variable. If IFS is un‐
set, the parameters are separated by spaces. If IFS is null, the parameters are joined without intervening sepa‐
rators. pands each positional parameter to a separate word; if not within double quotes, these words are subject to word
splitting. In contexts where word splitting is not performed, this expands to a single word with each positional
parameter separated by a space. When the expansion occurs within double quotes, each parameter expands to a sepa‐
rate word. That is, "$@" is equivalent to "$1" "$2" ... If the double-quoted expansion occurs within a word, the
expansion of the first parameter is joined with the beginning part of the original word, and the expansion of the
last parameter is joined with the last part of the original word. When there are no positional parameters, "$@"
and $@ expand to nothing (i.e., they are removed).
Insbesondere die markierten Stellen. Daraus verstehe ich „"$*" ist ein string (also kein array wie $@)“ und daher sollten sich diese beiden Programme gleich verhalten: # P1
X=$*
echo "${X%e}" und # P2
echo "${*%e} Tun sie aber nicht. Es geht mir nicht um die Lösung der Aufgabe, hinten etwas abzuschneiden. Das löst bereits das Programm P1. Es geht mir nur darum, den Fehler in meinem Verständnis zu fassen. Oder einen Bug in bash. shiro ist wohl schon auf der richtigen Fährte. Er schrieb: Die Parameter sind ein normales Array. Wenn du die Array-Elemente einer Variablen zuweist, klappt das Löschen des letzten "e", wie du auch festgestellt hast, sonst wird jedes einzelne Element behandelt.
Seine Worte liegen mir aber noch unverstanden im Hirn.
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11278
|
kB schrieb: Insbesondere die markierten Stellen. Daraus verstehe ich „"$*" ist ein string (also kein array wie $@)“ und daher sollten sich diese beiden Programme gleich verhalten
Innerhalb der ${} Umgebung für die Parameter-Expansion findet so wie ich das verstehe noch keine Expansion des Wertes des Arrays für die Shell statt, bei der ein String unter Berücksichtigung des IFS aus dem Array erzeugt werden würde - das passiert erst, nachdem die Parameter Expansion stattgefunden hat - so sieht man das recht gut:
| #!/usr/bin/bash
echo 'Argumente aus ${*%e}'
printf "@%s@\n" ${*%e}
echo 'Argumente aus "${*%e}"'
printf "@%s@\n" "${*%e}"
echo 'Argumente aus ${#%e}'
printf "@%s@\n" ${@%e}
echo 'Argumente aus "${#%e}"'
printf "@%s@\n" "${@%e}"
|
$ bash test.sh 1 e2 ea ae 3e '4e 5e' eeee
Argumente aus ${*%e}
@1@
@e2@
@ea@
@a@
@3@
@4e@
@5@
@eee@
Argumente aus "${*%e}"
@1 e2 ea a 3 4e 5 eee@
Argumente aus ${#%e}
@1@
@e2@
@ea@
@a@
@3@
@4e@
@5@
@eee@
Argumente aus "${#%e}"
@1@
@e2@
@ea@
@a@
@3@
@4e 5@
@eee@
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13242
|
seahawk1986 schrieb: Man kann auch den Array mit Ausnahme des letzten Elements ausgeben:
| echo "${@:1:$((${#@} - 1))}"
|
Oh cool! Man kann es noch ein klitzekleines Bisschen vereinfachen | echo "${@:1:$(($# - 1))}"
|
😉
|
|
kB
Supporter, Wikiteam
(Themenstarter)
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 9837
|
rklm schrieb: seahawk1986 schrieb: Man kann auch den Array mit Ausnahme des letzten Elements ausgeben:
| echo "${@:1:$((${#@} - 1))}"
|
Oh cool! Man kann es noch ein klitzekleines Bisschen vereinfachen | echo "${@:1:$(($# - 1))}"
|
… und sogar das kann man wesentlich kürzer und verständlicher schreiben: echo ${*:1:$#-1}
bzw.: echo ${*::$#-1} Es löst aber leider alles nicht meine Aufgabenstellung, denn ich will kein Array bearbeiten, sondern eine Zeichenkette (string) manipulieren.
|
|
kB
Supporter, Wikiteam
(Themenstarter)
Anmeldungsdatum: 4. Oktober 2007
Beiträge: 9837
|
seahawk1986 schrieb: […] Innerhalb der ${} Umgebung für die Parameter-Expansion findet so wie ich das verstehe noch keine Expansion des Wertes des Arrays für die Shell statt, bei der ein String unter Berücksichtigung des IFS aus dem Array erzeugt werden würde - das passiert erst, nachdem die Parameter Expansion stattgefunden hat - so sieht man das recht gut: […]
Danke für Deine Ausarbeitung! Das hilft mir weiter. Ich hake das Thema mal ab mit dem Ergebnis: „Die man page von bash ist in dieser Beziehung missverständlich und ich bin darauf hereingefallen.“ Richtig ist wohl: Sowohl $@ wie auch $* stehen unabhängig vom Quoting immer in einem array-Kontext und Bearbeitungen erfolgen deshalb auch immer im array-Kontext. Wenn man – wie ich – in einem skalaren Kontext bearbeiten möchte, muss man diesen erst über Zuweisung an eine Hilfsvariable herstellen und dann bearbeiten: X=$* ; echo ${X%e}
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13242
|
kB schrieb:
Es löst aber leider alles nicht meine Aufgabenstellung, denn ich will kein Array bearbeiten, sondern eine Zeichenkette (string) manipulieren.
Das hast Du doch aber geschrieben:
Ich möchte in einem bash-Skript aus dem Argumentstring $* das letzte Wort abschneiden.
Das habe ich so verstanden, dass Du ein Skript mit mehreren Wörtern (und also mehreren Parametern) aufrufst und das letzte Wort (damit den letzten Parameter) loswerden willst. Vielleicht wäre ein konkretes Beispiel hilfreich gewesen, was Eingabe und gewünschte Ausgabe sind. kB schrieb:
Richtig ist wohl: Sowohl $@ wie auch $* stehen unabhängig vom Quoting immer in einem array-Kontext und Bearbeitungen erfolgen deshalb auch immer im array-Kontext.
Beide bezeichnen ein Array. Unterschied ist nur die Parameter Expansion innerhalb doppelter Anführungsstriche.
Wenn man – wie ich – in einem skalaren Kontext bearbeiten möchte, muss man diesen erst über Zuweisung an eine Hilfsvariable herstellen und dann bearbeiten: X=$* ; echo ${X%e}
Irgendwie auch logisch, dass Du eine Zeichenkette brauchst, wenn Du es als eine Zeichenkette bearbeiten willst. 😉
|
|
shiro
Supporter
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1303
|
Ich denke der Satz When the expansion occurs within double quotes, it expands to a sin‐ gle word
will exakt gelesen werden. Im "${name}" Konstrukt ist der "name" Eintrag nicht als Expansion erlaubt, da dies zu einer doppelten Substitution führen würde, die (zwar aus meiner Sicht wünschenswert aber) hier nicht unterstützt wird. Es müsste daher etwas wie ${"$*"%e} erlaubt sein, damit die oben beschriebene Expansion zum Einsatz kommen könnte. Da dies bei "${}" nicht implementiert ist, greift die "expansion with double quotes" erst nachdem ${*%e} die Einzelelemente bearbeitet hat mit dem bereits gezeigten Ergebnis. In der normalen indirekten Variablen-Zuweisung ist ein Konstrukt wie "var=wert;var1=var;var2=${$var1}" ja nicht erlaubt, um var2=wert zu erhalten. Hier wird somit die indirekte Referenz eingesetzt (z.B. "eval var2=\$$var1" oder einfacher "var2=${!var1}"). Leider hilft dieser Ansatz (eval) für das geschilderte Problem nicht weiter, da der "name" Eintrag eine Variable und kein gequoteter String sein darf (ein ${"abcde"%e} wird ja leider nicht erlaubt). In meinem Verständnis liegt daher keine Fehlimplementierung (bug) oder falsche Dokumentation der bestehenden bash Implementierung vor. Wohl könnte ich mir aber vorstellen, dass dies in die whishlist aufgenommen werden könnte (ein "tr [:upper:] [:lower:]" hat man ja auch mittlerweile als ${x,,[A-Z]} implementiert).
|