Bash -c mit Positionsparametern

15

In der Regel wird $0in einem Skript der Name des Skripts oder der Name des aufgerufenen Skripts (einschließlich des Pfads) festgelegt. Wenn ich jedoch bashmit der -cOption verwende, $0wird auf das erste der nach der Befehlszeichenfolge übergebenen Argumente gesetzt:

bash -c 'echo $0 ' foo bar 
# foo 

In der Tat scheint es, dass Positionsparameter verschoben wurden, aber einschließlich $0. Jedoch shiftin der Befehlsfolge wirkt sich nicht aus $0(wie normal):

bash -c 'echo $0; shift; echo $0 ' foo bar
# foo
# foo

Warum dieses anscheinend merkwürdige Verhalten für Befehlszeichenfolgen? Beachten Sie, dass ich nach dem Grund und der Begründung für die Implementierung eines solchen merkwürdigen Verhaltens suche.


Man könnte spekulieren, dass ein solcher Befehlsstring den $0normalerweise definierten Parameter nicht benötigt , so dass er aus Sparsamkeitsgründen auch für normale Argumente verwendet wird. In diesem Fall ist das Verhalten von shiftjedoch ungerade. Eine andere Möglichkeit besteht $0darin, das Verhalten von Programmen zu definieren (a la bashaufgerufen als shoder vimaufgerufen als vi), aber das kann nicht sein, da dies $0nur in der Befehlszeichenfolge und nicht von Programmen gesehen wird, die in dieser aufgerufen werden. Ich kann mir keinen anderen Verwendungszweck vorstellen $0, daher bin ich ratlos, dies zu erklären.

muru
quelle
Als Randnotiz habe ich die Verwendung von -für das $0Argument als eine Redewendung gesehen, wie in sh -c 'foo $1 $2' - a b. Auf diese Weise sieht es ziemlich normal aus (wenn Sie herausgefunden haben, was das -bedeutet)
Volker Siegel
Sie können echo 'echo the other side of this pipe globs "$@"' | sh -s -- *aber leider auch $0generell keinen einstellbaren Parameter mit der -sOption tream angeben ... Sie kann jedoch auf viele der gleichen Arten verwendet werden, wie dies xargsgenerell der Fall ist. Und noch andere.
mikeserv
@VolkerSiegel Normaler wäre es gewesen --, das hätte die übliche Interpretation von "Ab hier fängt die Argumente an" haben können, die man in einigen anderen Programmen sieht. Andererseits könnte das diejenigen verwirren, die nicht mit -cdem Gedanken vertraut sind , dass --tatsächlich diese Interpretation vorliegt.
muru

Antworten:

10

Dies gibt Ihnen die Möglichkeit, $0bei Verwendung eines Inline-Skripts festzulegen / auszuwählen . Sonst $0wäre es einfach bash.

Dann können Sie zum Beispiel tun:

$ bash -c 'wc -c < "${1?}"' getlength foo
4
$ bash -c 'wc -c < "${1?}"' getlength bar
getlength: bar: No such file or directory
$ bash -c 'wc -c < "${1?}"' getlength
getlength: 1: parameter null or not set

Das taten nicht alle Muscheln. Die Bourne-Shell hat es getan. In der Korn- (und Almquist-) Shell wurde stattdessen der erste Parameter ausgewählt $1. POSIX entschied sich schließlich für den Bourne-Weg, kshund ashDerivate kehrten später darauf zurück (mehr dazu unter http://www.in-ulm.de/~mascheck/various/find/#shell ). Das ist für eine lange Zeit für die bedeutete , dass sh(die auf dem System abhängig wurde auf der Grundlage der Bourne, Almquist oder Korn - Shell), Sie nicht wissen , ob das erste Argument ging in $0oder $1so für die Portabilität, mußte man Dinge wie:

sh -c 'echo foo in "$1"' foo foo

Oder:

sh -c 'shift "$2"; echo txt files are "$@"' tentative-arg0 3 2 *.txt

Zum Glück hat POSIX das neue Verhalten angegeben, in das das erste Argument eingeht $0, sodass wir es jetzt portabel machen können:

sh -c 'echo txt files are "$@"' meaningful-arg0-for-error *.txt
Stéphane Chazelas
quelle
Ich lief: bash -c 'echo txt files are "$@"' meaningful-arg0-for-error *.txtauf Ubuntu 14.04, bash version 4.3.11(1)-release, und ich habe: txt files are *.txt. Oo
muru
2
@muru, das ist richtig (und Sie haben wahrscheinlich keine txtDateien im aktuellen Verzeichnis). Siehe auchbash -c 'echo "${1?}"' foo
Stéphane Chazelas
Ah ja. Das ist ein praktisch nützliches Beispiel.
muru
1
sh -c 'shift "$2"; echo txt files are "$@"' tentative-arg0 3 2 *.txtist fantastisch erfinderisch!
Iruvar
Oder Sie verwenden eine völlig robuste Problemumgehung (vorgeschlagen von Stéphane Chazelas in comp.unix.shell) SHELL -c 'shift $1; command' 2 1 arg1 arg2 ... haha ...
mikeserv
3

Dieses Verhalten wird von POSIX definiert :

sh -c befehlsname [argument ...]

Liest Befehle aus dem Operanden command_string. Setzen Sie den Wert des Sonderparameters 0 (siehe Sonderparameter ) aus dem Wert des Operanden befehlsname und den Positionsparametern ($ 1, $ 2 usw.) nacheinander aus den verbleibenden Argumentoperanden.

Warum möchten Sie dieses Verhalten: Dies gleicht die Lücke zwischen einem Skript und einer -cZeichenfolge aus. Sie können direkt zwischen den beiden ohne Änderung des Verhaltens konvertieren. Andere Bereiche stützen sich darauf, dass diese identisch sind.

Dies exechängt auch damit zusammen $0, wie Programmargumente im Allgemeinen funktionieren: Letztendlich wird eine der Funktionen aufgerufen, in denen sich auch das erste bereitgestellte Argument befindet , und ebenso häufig ist dieses Argument dasselbe wie das ausführbare Programm, das Sie ausführen. Manchmal möchten Sie jedoch einen besonderen Wert, und es gibt einfach keinen anderen Weg, ihn zu erhalten. Vorausgesetzt, dass das Argument existiert, muss es auf etwas abgebildet werden, und der Benutzer muss in der Lage sein, festzulegen, was das ist.

Diese Konsistenz (und der wahrscheinliche historische Unfall) führen zu der Situation, die Sie finden.

Michael Homer
quelle
Können Sie ein (hoffentlich reales) Beispiel für den zweiten Absatz geben?
Muru
Vorsicht bei der Analogie mit argv[0]. $0wird nur auf den übergebenen argv[0]Wert gesetzt, execve()wenn es wie shoder ist bash. Wird für Skripte $0auf den Pfad gesetzt, der als Argument 1, 2 ... für den Interpreter angegeben ist (und für Skripte, die direkt ausgeführt werden, der vom ersten Pfadargument stammt, um () auszuführen).
Stéphane Chazelas