Wie man über Argumente in einem Bash-Skript iteriert
Ich habe einen komplexen Befehl, den ich gerne in ein Shell/Bash-Skript umwandeln würde. Ich kann ihn leicht in Form von "$1" schreiben:
foo $1 args -o $1.ext
Ich möchte in der Lage sein, mehrere Eingaben an das Skript zu übergeben. Was ist der richtige Weg, dies zu tun?
Und natürlich möchte ich Dateinamen mit Leerzeichen in ihnen zu behandeln.
830
3
Verwenden Sie
"$@"
, um alle Argumente darzustellen:Damit wird jedes Argument durchlaufen und in einer eigenen Zeile ausgedruckt. $@ verhält sich wie $*, mit dem Unterschied, dass die Argumente bei Anführungszeichen richtig aufgeteilt werden, wenn sie Leerzeichen enthalten:
Neuauflage einer inzwischen gelöschten Antwort von VonC. Robert Gamble's prägnante Antwort geht direkt auf die Frage ein. In dieser wird auf einige Probleme mit Dateinamen, die Leerzeichen enthalten, eingegangen. Siehe auch: ${1:+"$@"} in /bin/sh Grundthese:
"$@"
ist richtig, und$*
(ohne Anführungszeichen) ist fast immer falsch. Das liegt daran, dass"$@"
gut funktioniert, wenn Argumente Leerzeichen enthalten, und genauso funktioniert wie$*
, wenn sie keine Leerzeichen enthalten. Unter bestimmten Umständen ist"$*"
auch OK, aber"$@"
funktioniert normalerweise (aber nicht immer) an denselben Stellen. Unquotiert sind "$@" und "$" gleichwertig (und fast immer falsch). Was ist also der Unterschied zwischen `$,
$@,
"$"und
"$@"? Sie beziehen sich alle auf 'alle Argumente für die Shell', aber sie tun unterschiedliche Dinge. Wenn sie nicht in Anführungszeichen gesetzt sind, tun
$und
$@das Gleiche. Sie behandeln jedes 'Wort' (eine Folge von Nicht-Leerzeichen) als ein separates Argument. Die zitierten Formen sind jedoch recht unterschiedlich:
"$"behandelt die Argumentliste als eine einzige durch Leerzeichen getrennte Zeichenkette, während
"$@"die Argumente fast genau so behandelt, wie sie in der Befehlszeile angegeben wurden.
"$@"expandiert zu nichts, wenn keine Positionsargumente vorhanden sind;
"$"expandiert zu einer leeren Zeichenkette — und ja, es gibt einen Unterschied, auch wenn er schwer zu erkennen sein kann. Weitere Informationen finden Sie weiter unten, nach der Einführung des (nicht standardisierten) Befehls
al. **Zweitrangige These:** Wenn Sie Argumente mit Leerzeichen verarbeiten und dann an andere Befehle weitergeben müssen, dann brauchen Sie manchmal Nicht-Standard Werkzeuge zur Unterstützung. (Oder Sie sollten Arrays verwenden, vorsichtig:
"${array[@]}"verhält sich analog zu
"$@"`.) Beispiel:Warum funktioniert das nicht? Es funktioniert nicht, weil die Shell Anführungszeichen verarbeitet, bevor sie die Variablen. Um also die Shell dazu zu bringen, die in
$var
eingebetteten Anführungszeichen zu beachten, müssen Sieeval
verwenden:Richtig knifflig wird es, wenn man Dateinamen wie "
Er sagte, "Don't do this!"
" (mit Anführungszeichen und doppelten Anführungszeichen und Leerzeichen).Die Shells (alle) machen es einem nicht gerade leicht, mit so etwas umzugehen zu handhaben, so dass (lustigerweise) viele Unix-Programme keine gute Arbeit bei der mit ihnen umgehen. Unter Unix kann ein Dateiname (eine einzelne Komponente) alle Zeichen enthalten außer Schrägstrich und NUL
'\0'
enthalten. Die Shells empfehlen jedoch dringend, keine Leerzeichen, Zeilenumbrüche oder Tabulatoren in einem Pfadnamen zu vermeiden. Das ist auch der Grund, warum Standard-Unix-Dateinamen keine Leerzeichen usw. enthalten. Beim Umgang mit Dateinamen, die Leerzeichen und andere problematische Zeichen enthalten können und andere problematische Zeichen enthalten, muss man extrem vorsichtig sein, und ich habe Ich habe vor langer Zeit herausgefunden, dass ich ein Programm brauche, das unter Unix nicht Standard ist. Ich nenne esescape
(Version 1.1 wurde 1989-08-23T16:01:45Z datiert). Hier ist ein Beispiel fürescape
im Einsatz - mit dem SCCS-Kontrollsystem. Es ist ein Cover-Skript, das sowohl eindelta
(man denke an einchecken) als auch einget
(denke Auschecken) durchführt. Verschiedene Argumente, insbesondere-y
(der Grund, warum Sie die Änderung vorgenommen haben) würden Leerzeichen und Zeilenumbrüche enthalten. Beachten Sie, dass das Skript aus dem Jahr 1992 stammt und daher Back-Ticks anstelle von$(cmd ...)
und verwendet nicht#!/bin/sh
in der ersten Zeile.(Heutzutage wuerde ich wahrscheinlich nicht mehr so ausfuehrlich Escape verwenden - es ist zum Beispiel nicht mit dem Argument
-e
benötigt - aber insgesamt ist dies eines meiner einfacheren Skripte, dasescape
verwendet). Das Programmescape
gibt seine Argumente einfach aus, ähnlich wieecho
tut, aber es stellt sicher, dass die Argumente für die Verwendung miteval
geschützt sind (eine Ebene voneval
; ich habe ein Programm, das eine entfernte Shell ausführt, und das musste die Ausgabe vonescape
escapen).Ich habe ein anderes Programm namens
al
, das seine Argumente in einer Zeile auflistet (und es ist noch viel älter: Version 1.1 vom 1987-01-27T14:35:49). Es ist sehr nützlich beim Debuggen von Skripten, da es in eine Befehlszeile eingefügt werden kann, um zu sehen, welche Argumente tatsächlich an den Befehl übergeben werden.[Hinzufügen: Und nun, um den Unterschied zwischen den verschiedenen
"$@"
-Notationen zu zeigen, hier ein weiteres Beispiel:Beachten Sie, dass die ursprünglichen Leerzeichen zwischen
*
und*/*
in der Befehlszeile nicht erhalten bleiben. Beachten Sie auch, dass Sie die 'Kommandozeilenargumente' in der Shell ändern können, indem Sie verwenden:Dies setzt 4 Optionen, '
-neu
', '-opt
', 'und
', und 'arg mit Leerzeichen
'.] Hmm, das ist eine ziemlich lange Antwort - vielleicht ist Exegese der bessere Begriff. Quellcode für
escape
auf Anfrage erhältlich (email to firstname dot nachname at gmail dot com). Der Quellcode füral
ist denkbar einfach:Das'ist alles. Es ist äquivalent zu dem Skript
test.sh
, das Robert Gamble gezeigt hat, und könnte als Shell-Funktion geschrieben werden (aber Shell-Funktionen gab es in der lokalen Version der Bourne-Shell nicht, als ichal
schrieb). Beachten Sie auch, dass Sieal
als einfaches Shell-Skript schreiben können:Die Bedingung ist notwendig, damit es keine Ausgabe erzeugt, wenn keine Argumente übergeben werden. Der Befehl
printf
erzeugt eine leere Zeile mit nur dem Argument format string, aber das C-Programm erzeugt nichts.Beachten Sie, dass Robert's Antwort korrekt ist, und es funktioniert auch in
sh
. Sie können es (portabel) noch weiter vereinfachen:ist äquivalent zu:
D.h., Sie brauchen nichts!
Testen (
$
ist die Eingabeaufforderung):Das erste Mal habe ich darüber in Unix Programming Environment von Kernighan und Pike gelesen.
In
bash
ist dies inhelp for
dokumentiert: