|
Dalai
Anmeldungsdatum: 16. Juni 2008
Beiträge: 2321
|
Hallo Leute, ich brauche mal wieder eure Hilfe und euren Sachverstand. Gegeben sei ein Bash-Skript, in dem eine Variable einen Inhalt haben kann aber nicht muss. Sofern diese Variable nicht leer ist, soll deren Inhalt in eine weitere Variable (im Beispiel unten ist das $params) übernommen werden und ein Schalter vorangestellt werden. Diese zweite Variable soll an ein zu startendes Kommando übergeben werden. Im Beispiel unten ist das openssl, aber die Problematik bezieht sich generell auf alle Kommandos, die Argumente zu ihren Schaltern bieten (z.B. auch rsync). Beispiel-Code, der das Problem bzw. die Fragestellung verdeutlichen soll:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 | #!/bin/bash
pfx="/tmp/ssl-cert-snakeoil.pfx"
key=/etc/ssl/private/ssl-cert-snakeoil.key
cert=/etc/ssl/certs/ssl-cert-snakeoil.pem
password="/tmp/.sinnloses password"
echo "blub">"${password}"
set -x
# test with non-empty var
echo "non-empty var #1"
params="-passout file:\"${password}\""
openssl pkcs12 -export ${params} -inkey "${key}" -in ${cert} -out "${pfx}"
echo "non-empty var #2"
params=-passout file:'/tmp/.sinnloses password'
openssl pkcs12 -export ${params} -inkey "${key}" -in ${cert} -out "${pfx}"
echo "non-empty var #3"
params=-passout file:"${password}"
openssl pkcs12 -export ${params} -inkey "${key}" -in ${cert} -out "${pfx}"
echo "non-empty var #4, array"
params=( -passout file:"${password}" )
openssl pkcs12 -export "${params[@]}" -inkey "${key}" -in ${cert} -out "${pfx}"
echo "non-empty var #5, array"
params=( -passout file:\"${password}\" )
openssl pkcs12 -export "${params[@]}" -inkey "${key}" -in ${cert} -out "${pfx}"
rm "${password}"
rm "${pfx}"
set +x
|
Ausgabe: + echo 'non-empty var #1'
non-empty var #1
+ params='-passout file:"/tmp/.sinnloses password"'
+ openssl pkcs12 -export -passout 'file:"/tmp/.sinnloses' 'password"' -inkey /etc/ssl/private/ssl-cert-snakeoil.key -in /etc/ssl/certs/ssl-cert-snakeoil.pem -out /tmp/ssl-cert-snakeoil.pfx
pkcs12: Use -help for summary.
+ echo 'non-empty var #2'
non-empty var #2
+ params=-passout
+ 'file:/tmp/.sinnloses password'
./test_switch+argument2.sh: Zeile 18: file:/tmp/.sinnloses password: Datei oder Verzeichnis nicht gefunden
+ openssl pkcs12 -export -passout 'file:"/tmp/.sinnloses' 'password"' -inkey /etc/ssl/private/ssl-cert-snakeoil.key -in /etc/ssl/certs/ssl-cert-snakeoil.pem -out /tmp/ssl-cert-snakeoil.pfx
pkcs12: Use -help for summary.
+ echo 'non-empty var #3'
non-empty var #3
+ params=-passout
+ 'file:/tmp/.sinnloses password'
./test_switch+argument2.sh: Zeile 22: file:/tmp/.sinnloses password: Datei oder Verzeichnis nicht gefunden
+ openssl pkcs12 -export -passout 'file:"/tmp/.sinnloses' 'password"' -inkey /etc/ssl/private/ssl-cert-snakeoil.key -in /etc/ssl/certs/ssl-cert-snakeoil.pem -out /tmp/ssl-cert-snakeoil.pfx
pkcs12: Use -help for summary.
+ echo 'non-empty var #4, array'
non-empty var #4, array
+ params=(-passout file:"${password}")
+ openssl pkcs12 -export -passout 'file:/tmp/.sinnloses password' -inkey /etc/ssl/private/ssl-cert-snakeoil.key -in /etc/ssl/certs/ssl-cert-snakeoil.pem -out /tmp/ssl-cert-snakeoil.pfx
non-empty var #5, array
+ params=(-passout file:\"${password}\")
+ openssl pkcs12 -export -passout 'file:"/tmp/.sinnloses' 'password"' -inkey /etc/ssl/private/ssl-cert-snakeoil.key -in /etc/ssl/certs/ssl-cert-snakeoil.pem -out /tmp/ssl-cert-snakeoil.pfx
pkcs12: Use -help for summary.
+ rm '/tmp/.sinnloses password'
+ rm /tmp/ssl-cert-snakeoil.pfx
+ set +xZiel soll sein, die Kommandozeile "zusammenzubasteln":
openssl pkcs12 -export -passout file:"/tmp/.sinnloses password" -inkey .... Wie macht man sowas richtig? Selbst im letzten Fall #4, wo ein Array verwendet wird, fehlen die Quotes um den Dateinamen zumindest in der Ausgabe, stellt mich daher nicht so recht zufrieden, aber Backslashes zum Escapen der Quotes funktionieren ebenfalls nicht, siehe Fall #5. Grüße
Dalai
|
|
schragge
Anmeldungsdatum: 27. Januar 2022
Beiträge: Zähle...
|
Ein Leezeichen in .sinnloses password verderbt alles. Brauchst du wirklich ein Leerzeichen dort zu haben? Dalai schrieb: Selbst im letzten Fall #4, wo ein Array verwendet wird, fehlen die Quotes um den Dateinamen zumindest in der Ausgabe, stellt mich daher nicht so recht zufrieden
Array ist schon richtig. Du brauchst keine Quotes in diesem Fall. Sie sind nicht Teil von der openssl-Syntax. Andere Option is eval. Die ist aber gefährlich, und ich würde sie hier nicht nutzen.
|
|
Doc_Symbiosis
Anmeldungsdatum: 11. Oktober 2006
Beiträge: 4453
|
Vielleicht hilft Dir eines dieser Konstrukte, womit das schön kurz gelöst werden kann:
# Ergibt "text", wenn var leer oder nicht existiert, sonst ${var}
${var:-text}
# Ergibt "", falls var leer oder nicht existiert, sonst "text"
${var:+text}
# var="text", falls var leer ist oder nicht existiert, sonst ${var}
${var:=text}
|
|
Dalai
(Themenstarter)
Anmeldungsdatum: 16. Juni 2008
Beiträge: 2321
|
schragge schrieb: Ein Leezeichen in .sinnloses password verderbt alles. Brauchst du wirklich ein Leerzeichen dort zu haben?
Das Beispiel ist inhaltlich, wie der Dateiname schon sagt, sinnlos. Das Argument zum Schalter, hier der Dateiname zur Passwort-Datei, wird letztlich über eine Konfigurationsdatei eingelesen, und im Dateinamen und/oder Pfad können natürlich Leerzeichen oder andere Zeichen, die ein Quoting erfordern, enthalten sein.
Array ist schon richtig. Du brauchst keine Quotes in diesem Fall. Sie sind nicht Teil von der openssl-Syntax.
Es geht mir nicht um die Syntax eines konkreten Programms sondern um die Verallgemeinerung. Anderes Beispiel, das ich auch schon hatte, war rsync -e "${andere_shell}"Wie packt man nun '-e "{andere_shell}"' in eine Variable, wenn ${andere_shell} Leerzeichen bzw. weitere Schalter/Parameter beinhalten könnte? Grüße
Dalai
|
|
schragge
Anmeldungsdatum: 27. Januar 2022
Beiträge: Zähle...
|
Doc_Symbiosis Nein, es hat damit nichts zu tun. Dasselbe Problem ist z.B. in diesem Thread 🇬🇧 beschrieben. Dort hab ich eval vorgeschlagen. Man kann in Verbindung mit eval auch ${var@Q} bzw. printf -v var2 %q "$var1" nutzen. Ein Array ist aber viel besser.
|
|
schragge
Anmeldungsdatum: 27. Januar 2022
Beiträge: Zähle...
|
Dalai schrieb:
Wie packt man nun '-e "${andere_shell}"' in eine Variable, wenn ${andere_shell} Leerzeichen bzw. weitere Schalter/Parameter beinhalten könnte?
Es gibt etliche Möglichkeiten.
Ein Array eval (z.B. wer getopt nutzt, kennt den Ausdruck eval set -- "$opts")
Man verteilt die zu konstruierende Kommandozeile zwischen mehreren Variablen, so dass man einzelne Teile quoten kann. Z-Shell statt Bash (dort findet Worttrennung vor dem Auswerten der Variablen statt)
|
|
Dalai
(Themenstarter)
Anmeldungsdatum: 16. Juni 2008
Beiträge: 2321
|
OK, ich hab das jetzt nochmal anhand eines anderen Beispiels getestet und komme zum Schluss, dass es mit Arrays wohl wahrscheinlich funktioniert. Ich formuliere das so vorsichtig, weil ich nicht sicher sein kann, dass es in allen Fällen funktioniert. Weiteres verkürztes und inhaltlich sinnloses Beispiel, nur zu Demonstrationszwecken:
| #!/bin/bash
rsh="ssh -v"
params=""
if [[ -n "${rsh}" ]]; then
params=( -e "${rsh}" )
fi
echo rsync --archive "${params[@]}" -v wartung@localhost:/media/daten/scripts/ /tmp/
rsync --archive "${params[@]}" -v wartung@localhost:/media/daten/scripts/ /tmp/
|
Irritierend daran ist, dass echo die Variablen in der Ausgabe nicht quotet, so dass man denkt, sie werden nicht gesetzt. Darf ich also annehmen, dass das nur ein Ausgabeproblem ist, das Kommando dennoch wie gedacht, also mit Anführungszeichen, ausgeführt wird? Oder geht diese Variante unter bestimmten Bedingungen auch wieder nicht? Ein set -x zu Beginn des Skripts lässt ersteres vermuten. Grüße
Dalai
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11278
|
Anführungszeichen werden von der Shell bei der Evaluation eines Befehls entfernt, die dienen nur dazu in einem String einzelne Elemente voneinander abzugrenzen, damit die Shell dann unter Zuhilfenahme des IFS, an der der zu evaluierende String aufgetrennt wird, null-terminierte C-Strings draus basteln kann. Am besten baust du dir einen Dummy-Befehl, der die Argumente auflistet, die er bekommt - also z.B.:
| function showargs () {
python3 - "$@" << EOF
import sys
for n, arg in enumerate(sys.argv[1:], start=1):
print("Argument {}: »{}«".format(n, arg))
EOF
}
|
Und dann wird das klar, wie die einzelnen Argumente in deinem Beispiel aussehen:
$ showargs --archive "${params[@]}" -v wartung@localhost:/media/daten/scripts/ /tmp/
Argument 1: »--archive«
Argument 2: »-e«
Argument 3: »ssh -v«
Argument 4: »-v«
Argument 5: »wartung@localhost:/media/daten/scripts/«
Argument 6: »/tmp/«
|
|
schragge
Anmeldungsdatum: 27. Januar 2022
Beiträge: 181
|
Dalai schrieb: Irritierend daran ist, dass echo die Variablen in der Ausgabe nicht quotet, so dass man denkt, sie werden nicht gesetzt.
Mit echo kannst du das nicht testen. Man testet es am besten mit printf oder mit einer for-Schleife:
| rsync_opts=(-av -e 'ssh -l ssh-user')
printf %s\\n "${rsync_opts[@]}"
|
Siehe auch diese Diskussion 🇬🇧 @LQ.
|
|
frostschutz
Anmeldungsdatum: 18. November 2010
Beiträge: 7795
|
oder mit dem debug modus der shell (set -x) $ set -x
$ echo a "b c" d
+ echo a 'b c' d
a b c d
|
|
Dalai
(Themenstarter)
Anmeldungsdatum: 16. Juni 2008
Beiträge: 2321
|
Naja, printf quotet die Ausgabe ebensowenig wie echo. Ja, die Parameter sind auf separaten Zeilen, was die Zusammengehörigkeit klarmacht. Dann werd ich sowas wie | function showargs()
{
declare -i i=0
for a in "$@"; do
i+=1
echo "parameter $i = $a"
done
}
|
in mein Standardprogramm zum Debuggen aufnehmen, jedenfalls für solche Fälle. Die Variante mit Python und Zähler ist natürlich schicker 😀. Vielen Dank für die tatkräftige Unterstützung und Erklärungen von allen Seiten! Dann werd ich das in den fraglichen Skripten mit Arrays umsetzen. Grüße
Dalai
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13242
|
Dalai schrieb:
Wie macht man sowas richtig?
Lösung 4 ist "richtig".
Selbst im letzten Fall #4, wo ein Array verwendet wird, fehlen die Quotes um den Dateinamen zumindest in der Ausgabe, stellt mich daher nicht so recht zufrieden, aber Backslashes zum Escapen der Quotes funktionieren ebenfalls nicht, siehe Fall #5.
Aber Deine Ausgabe zeigt doch die Quotes + echo 'non-empty var #4, array'
non-empty var #4, array
+ params=(-passout file:"${password}")
+ openssl pkcs12 -export -passout 'file:/tmp/.sinnloses password' -inkey /etc/ssl/private/ssl-cert-snakeoil.key -in /etc/ssl/certs/ssl-cert-snakeoil.pem -out /tmp/ssl-cert-snakeoil.pfxDas funktioniert sauber: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | $ name='fooo bar'
$ params=(-switch 'value with space' "$name" "prefix:$name")
$ args.sh -a "${params[@]}" -b
6 args
arg[1]: <-a>
arg[2]: <-switch>
arg[3]: <value with space>
arg[4]: <fooo bar>
arg[5]: <prefix:fooo bar>
arg[6]: <-b>
$ ( set -x; args.sh -a "${params[@]}" -b)
+ args.sh -a -switch 'value with space' 'fooo bar' 'prefix:fooo bar' -b
6 args
arg[1]: <-a>
arg[2]: <-switch>
arg[3]: <value with space>
arg[4]: <fooo bar>
arg[5]: <prefix:fooo bar>
arg[6]: <-b>
|
|
|
Dalai
(Themenstarter)
Anmeldungsdatum: 16. Juni 2008
Beiträge: 2321
|
rklm schrieb: Aber Deine Ausgabe zeigt doch die Quotes + echo 'non-empty var #4, array'
non-empty var #4, array
+ params=(-passout file:"${password}")
+ openssl pkcs12 -export -passout 'file:/tmp/.sinnloses password' -inkey /etc/ssl/private/ssl-cert-snakeoil.key -in /etc/ssl/certs/ssl-cert-snakeoil.pem -out /tmp/ssl-cert-snakeoil.pfx
Ja, es sind Quotes enthalten, aber nicht an den Stellen, wo sie ursprünglich gesetzt wurden (und auch nicht derselbe Typ Quotes). Aber das wird wohl mit der Interpretation und Verarbeitung durch die Bash - Word splitting, Quoting und Expandierung von Variablen - zusammenhängen. Genau diese fehlenden bzw. falschen Quotes um den Dateinamen haben mich irritiert und annehmen lassen, dass es nicht der richtige Weg ist. Ich hoffe, das ist nachvollziehbar. Grüße
Dalai
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13242
|
Dalai schrieb: rklm schrieb: Aber Deine Ausgabe zeigt doch die Quotes + echo 'non-empty var #4, array'
non-empty var #4, array
+ params=(-passout file:"${password}")
+ openssl pkcs12 -export -passout 'file:/tmp/.sinnloses password' -inkey /etc/ssl/private/ssl-cert-snakeoil.key -in /etc/ssl/certs/ssl-cert-snakeoil.pem -out /tmp/ssl-cert-snakeoil.pfx
Ja, es sind Quotes enthalten, aber nicht an den Stellen, wo sie ursprünglich gesetzt wurden (und auch nicht derselbe Typ Quotes).
Das muss ja auch gar nicht. Die Ausgabe von set -x dient ja dem Debugging. Und dafür ist wichtig, dass man sieht, was an einen Prozess übergeben wird. Zu dem Zeitpunkt sind ja alle Parameter Expansions usw. bereits erledigt. Der aufgerufene Prozess kann das sowieso nicht unterscheiden.
Aber das wird wohl mit der Interpretation und Verarbeitung durch die Bash - Word splitting, Quoting und Expandierung von Variablen - zusammenhängen. Genau diese fehlenden bzw. falschen Quotes um den Dateinamen haben mich irritiert und annehmen lassen, dass es nicht der richtige Weg ist. Ich hoffe, das ist nachvollziehbar.
Das sind keine fehlenden oder falschen Quotes. Man muss sich klar machen, wie die Shell arbeitet. Wie Du schon sagst, gibt es ja diverse Operationen (Word Splitting, Parameter Expansion usw.), die die Shell ausführt, bevor sie ein Kommando aufruft. Das Kommando sieht dann nur noch eine Liste von Zeichenketten. Das sieht man an der Signatur der Funktionen der exec()-Familie. Selbst solche scheinbar komplizierten Ausdrücke werden so eingedampft und ergeben letztlich dasselbe: | $ set -x
$ echo "$LC_NAME:"'$PATH'\ text' with space and'\'
+ echo 'de_DE.UTF-8:$PATH text with space and'\'''
de_DE.UTF-8:$PATH text with space and'
$ echo "$LC_NAME:"'$PATH'" text with space and'"
+ echo 'de_DE.UTF-8:$PATH text with space and'\'''
de_DE.UTF-8:$PATH text with space and'
$ echo "$LC_NAME:"'$PATH'\ text' with '"space and'"
+ echo 'de_DE.UTF-8:$PATH text with space and'\'''
de_DE.UTF-8:$PATH text with space and'
|
Du kannst gerne das Skript von meiner Nutzerseite nehmen um die einzelnen Argumente auszugeben. Im Prinzip siehst Du da dasselbe wie bei set -x, nur indiziert und auf mehrere Zeilen verteilt.
|
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17630
|
Dalai schrieb: Hallo Leute, ich brauche mal wieder eure Hilfe und euren Sachverstand.
Vielleicht hilft Dir noch eine weitere Betrachtungsperspektive.
Im Beispiel unten ist das openssl, aber die Problematik bezieht sich generell auf alle Kommandos, die Argumente zu ihren Schaltern bieten (z.B. auch rsync).
Das ist leider kein gutes Beispiel, denn man übernimmt nicht gerne ein Bündel ssl-Aufrufe in seine Shell und probiert mal schnell aus. Andererseits ist es auch kein so schlechtes Beispiel, denn die erste Idee, es sei etwas gefragt, was Doc_Symbiosis nicht falsch, aber themaverfehlend beantwortet hat, wird durch das konkrete Beispiel negiert. Es geht eher um das Gegenteil. Beispiel-Code, modifiziert | params1="-passout file:\"${password}\""
params2=-passout file:'/tmp/.sinnloses password'
params3=-passout file:"${password}"
params4=( -passout file:"${password}" )
params5=( -passout file:\"${password}\" )
|
Nummer 2 und 3 kann man sofort ausschließen. Bei der Zuweisung einer Variablen (x=y) liest der Parser das y, und sobald er einem Space begegnet ist für ihn die Zuweisung beendet und danach kommt entweder nichts mehr oder ein Befehl, außer das Leerzeichen war geschützt. (x=y z) wird y dem x zuweisen und versuchen z auszuführen. Das macht man, um beispielsweise eine englische Ausgabe bei stat zu erzielen, bei der access-, modify- und change-time sauber untereinander stehen:
Das stat DATEI ist ein eigenes Kommando für das man vorab eine Variable (LC_ALL) definiert, und auf C setzt. Schützen kann man Leerzeichen mit Anführungsstrichen, Apostrophen oder einem Backslash direkt davor. Das ist jetzt nur eine Teilantwort, aber dafür noch leicht verständlich. ☺
|