2016-05-21 8 views
4

Ich versuche, ein Bash Wrapper Skript zu schreiben, das sehr sorgfältig den Wert von argv [0]/$ 0 nachahmt. Ich verwende exec -a, um ein separates Programm mit dem argv [0] -Wert des Wrappers auszuführen. Ich finde, dass Bashs $ 0 manchmal nicht den gleichen Wert liefert wie ich in einem C-Programm argv [0]. Hier ist ein einfaches Testprogramm, das den Unterschied in den beiden C und bash demonstriert:argv [0] mit Bash sorgfältig nachahmen

int main(int argc, char* argv[0]) 
{ 
    printf("Argv[0]=%s\n", argv[0]); 
    return 0; 
} 

und

#!/bin/bash 
echo \$0=$0 

Wenn diese Programme mit dem vollständigen (absoluten oder relativen) Laufweg auf den binären, sie verhalten sich die gleiche: Aber

$ /path/to/printargv 
Argv[0]=/path/to/printargv 

$ /path/to/printargv.sh 
$0=/path/to/printargv.sh 

$ to/printargv 
Argv[0]=to/printargv 

$ to/printargv.sh 
$0=to/printargv.sh 

, wenn sie aufgerufen wird, als ob sie auf dem Weg sind, erhalte ich unterschiedliche Ergebnisse:

$ printargv 
Arv[0]=printargv 

$ printargv.sh 
$0=/path/to/printargv.sh 

Zwei Fragen:

1) Ist dieses Verhalten vorgesehen, die erklärt werden kann, oder ist das ein Fehler? 2) Was ist der "richtige" Weg, um das Ziel zu erreichen, argv [0] sorgfältig nachzuahmen?

bearbeiten: Tippfehler.

Antwort

2

Was Sie sehen, hier ist das dokumentierte Verhalten von bash und execve (zumindest ist es auf Linux dokumentiert und FreeBSD, nehme ich an, dass andere Systeme ähnliche Dokumentation haben), und spiegelt die verschiedenen Möglichkeiten, die argv[0] aufgebaut ist.

Bash (wie jede andere Shell) konstruiert argv von der bereitgestellten Befehlszeile, nach der Durchführung der verschiedenen Erweiterungen, resplit Wörter wie nötig, und so weiter. Das Endergebnis ist, dass, wenn Sie

printargv 

argv Typ als { "printargv", NULL } konstruiert und wenn Sie

to/printargv 

argv Typ als { "to/printargv", NULL } aufgebaut. Also keine Überraschungen dort.

Aber der Ausführungspfad abweicht, an diesem Punkt (In beiden Fällen gab es Kommandozeilenargumente gewesen, würden sie in argv beginnend an Position 1. erschienen). Wenn das erste Wort in der Befehlszeile ein/enthält, wird es als Dateiname betrachtet, entweder relativ oder absolut. Die Shell führt keine weitere Verarbeitung durch; Es ruft einfach execve mit dem angegebenen Dateinamen als sein filename Argument und argv Array zuvor als sein argv Argument aufgebaut. In diesem Fall entspricht argv[0] genau auf die filename

Aber wenn der Befehl hat keine Schrägstriche:

printargv 

die Schale macht viel mehr Arbeit:

  • Erstens, es prüft, wenn der Name eine benutzerdefinierte Shell-Funktion ist.Wenn dies der Fall ist, wird es ausgeführt, wobei $1...$n aus dem bereits erstellten Array argv entnommen wird. ($0 weiterhin argv[0] von der Skriptaufruf, obwohl.)

  • Dann überprüft es, ob der Name ein integrierter Bash-Befehl ist. Wenn ja, führt es es aus. Die Interaktion von Befehlszeilenargumenten mit Befehlszeilenargumenten ist für diese Antwort nicht möglich und nicht wirklich vom Benutzer sichtbar.

  • Schließlich versucht es, das externe Dienstprogramm zu finden, das dem Befehl entspricht, indem es die Komponenten von $PATH durchsucht und nach einer ausführbaren Datei sucht. Wenn es einen findet, ruft es execve auf und gibt ihm den Pfad, der als das filename Argument gefunden wird, aber das argv Array weiterhin verwendet, das aus den Worten des Befehls besteht. Also in diesem Fall, filename und argv[0] tun nicht entsprechen.

also in beiden Fällen endet die Schale execve Aufruf, einen Filepath Bereitstellen (möglicherweise relativen) als filename Argument und das Wort-split-Befehl als argv Argument.

Wenn die angegebene Datei ein ausführbares Bild ist, gibt es wirklich nichts mehr zu sagen. Das Bild wird in den Speicher geladen, und sein main wird mit dem bereitgestellten Vektor argv aufgerufen. argv[0] wird ein einzelnes Wort oder ein relativer oder absoluter Pfad sein, abhängig nur von dem, was ursprünglich eingegeben wurde.

Aber wenn die angegebene Datei ein Skript ist, wird der Lader einen Fehler erzeugen und execve wird prüfen, ob die Datei mit einem Shebang (#!) beginnt. (Seit Posix 2008 wird execve auch versuchen, die Datei als Skript mit dem System-Shell zu laufen, als ob es #!/bin/sh als shebang Linie hatte.)

Hier ist die Dokumentation für execve auf Linux:

ein Interpreter Skript ist eine Textdatei, die Berechtigung aktiviert ausführen und erste Zeile hat, dessen ist die Form:

 #! interpreter [optional-arg] 

der Interpreter ein gültiger Pfad für eine ausführbare Datei sein muss. Wenn der Dateiname Argument von execve() einen Interpreter Skript gibt, dann wird Interpreter mit den folgenden Argumenten aufgerufen werden:

 interpreter [optional-arg] filename arg... 

wo arg... die Reihe von Wörtern ist, auf die das argv Argument von execve(), bei argv[1] beginnen.

Beachten Sie, dass in den oben genannten, die filename Argument ist die filename Argument execve. In Anbetracht der shebang Linie #!/bin/bash wir haben jetzt entweder

/bin/bash to/printargv   # If the original invocation was to/printargv 

oder

/bin/bash /path/to/printargv  # If the original invocation was printargv 

Beachten Sie, dass argv[0] effektiv verschwunden ist.

bash dann führt das Skript in der Datei aus. Vor dem Ausführen des Skripts setzt es $0 auf das Dateiname-Argument, in unserem Beispiel entweder to/printargv oder /path/to/printargv, und setzt $1...$n auf die verbleibenden Argumente, die von den Befehlszeilenargumenten in der ursprünglichen Befehlszeile kopiert wurden.

Zusammenfassend: Wenn Sie den Befehl mit einem Dateinamen ohne Schrägstriche aufrufen:

  • Wenn der Dateiname ein ausführbares Bild enthält, wird es argv[0] als Befehlsnamen wie getippt sehen.

  • Wenn der Dateiname ein Bash-Skript mit einer Shebang-Zeile enthält, zeigt das Skript $0 als tatsächlichen Pfad zur Skriptdatei an.

Wenn Sie den Befehl mit einem Dateinamen mit Schrägstrichen aufrufe, in beiden Fällen wird es sehen argv [0] als die Dateinamen wie eingegeben (was relativ sein könnte, aber offensichtlich hat immer einen Schrägstrich).

Wenn Sie ein Skript aufrufen, indem Sie explizit den Shell-Interpreter aufrufen (bash printargv), sieht das Skript $0 als Dateinamen, der nicht nur relativ sein kann, sondern auch keinen Schrägstrich haben darf.

All dies bedeutet, dass Sie "argv [0]" nur dann sorgfältig nachahmen können, wenn Sie wissen, welche Form des Aufrufs des Skripts Sie nachahmen möchten. (Es bedeutet auch, dass das Skript nie auf den Wert argv[0] verlassen sollte, aber das ist ein anderes Thema.)

Wenn Sie dies für Komponententests tun, sollten Sie eine Option angeben, welchen Wert als argv bereitzustellen 0]. Viele Shellskripte, die versuchen, $0 zu analysieren, gehen davon aus, dass es sich um einen Dateipfad handelt. Sie sollten das nicht tun, da es nicht sein könnte, aber da ist es. Wenn Sie diese Dienstprogramme ausrauchen möchten, sollten Sie einen Wert für den Wert $0 angeben. Andernfalls empfiehlt es sich, standardmäßig einen Pfad zur Skriptdatei anzugeben.

+0

Danke für die Antwort. printargs.sh hat tatsächlich einen Shebang. Ich habe es 2-Zeilen-Quellcode geschrieben. Dies lässt immer noch den wichtigen Teil der Frage: Was ist der richtige Weg, $ argv [0] mit einem Bash-Skript genau nachzuahmen? –

+0

@jason, yeah, sorry, ich wurde unterbrochen. Ich werde die Antwort in ein paar Stunden beenden. – rici

+0

@jason: OK, schrieb die Antwort um; ich hoffe es hilft. Ich glaube nicht, dass es einen "richtigen Weg" gibt, da argv [0] dies sein könnte oder auch, und idealerweise wird ein Bash-Skript mit beiden funktionieren. Wenn Sie also testen, sollten Sie das Skript mit verschiedenen Werten für $ 0 testen. Wenn Sie nur versuchen, etwas plausibel zu machen, ist mein Vorschlag, den vollständigen absoluten Dateinamen zu verwenden. – rici