Wie kann ich den Namen des Befehls abrufen, der für Eingabeaufforderungen in Ruby aufgerufen wird?

83

Ich habe vor einiger Zeit ein nettes kleines Ruby-Skript geschrieben, das ich ziemlich mag. Ich möchte seine Robustheit verbessern, indem ich nach der richtigen Anzahl von Argumenten suche:

if ARGV.length != 2 then
  puts "Usage: <command> arg1 arg2"
end

Das ist natürlich Pseudocode. Wie auch immer, in C oder C ++ könnte argv[0]ich den Namen abrufen, mit dem der Benutzer zu meinem Befehl gelangt ist, unabhängig davon, ob er ihn wie ./myScript.rboder myScript.rboder genannt hat /usr/local/bin/myScript.rb. In Ruby weiß ich, dass dies ARGV[0]das erste wahre Argument ist und ARGVnicht den Befehlsnamen enthält. Gibt es eine Möglichkeit, wie ich das bekommen kann?

adam_0
quelle

Antworten:

149

Ruby hat drei Möglichkeiten, uns den Namen des aufgerufenen Skripts zu geben:

#!/usr/bin/env ruby

puts "$0            : #{$0}"
puts "__FILE__      : #{__FILE__}"
puts "$PROGRAM_NAME : #{$PROGRAM_NAME}"

Das Speichern dieses Codes als "test.rb" und das Aufrufen auf verschiedene Arten zeigt, dass das Skript den Namen erhält, wie er vom Betriebssystem an ihn übergeben wurde. Ein Skript weiß nur, was das Betriebssystem sagt:

$ ./test.rb 
$0            : ./test.rb
__FILE__      : ./test.rb
$PROGRAM_NAME : ./test.rb

$ ~/Desktop/test.rb 
$0            : /Users/ttm/Desktop/test.rb
__FILE__      : /Users/ttm/Desktop/test.rb
$PROGRAM_NAME : /Users/ttm/Desktop/test.rb

$ /Users/ttm/Desktop/test.rb 
$0            : /Users/ttm/Desktop/test.rb
__FILE__      : /Users/ttm/Desktop/test.rb
$PROGRAM_NAME : /Users/ttm/Desktop/test.rb

Wenn Sie es ~im zweiten Beispiel mit der Verknüpfung für $ HOME aufrufen, wird es vom Betriebssystem durch den erweiterten Pfad ersetzt, der mit dem im dritten Beispiel übereinstimmt. In allen Fällen ist es das, was das Betriebssystem übergeben hat.

Das Verknüpfen mit der Datei über Hard- und Softlinks zeigt ein konsistentes Verhalten. Ich habe einen harten Link für test1.rb und einen weichen Link für test2.rb erstellt:

$ ./test1.rb 
$0            : ./test1.rb
__FILE__      : ./test1.rb
$PROGRAM_NAME : ./test1.rb

$ ./test2.rb 
$0            : ./test2.rb
__FILE__      : ./test2.rb
$PROGRAM_NAME : ./test2.rb

Das Starten ruby test.rbmit einer der Variationen des Skriptnamens liefert konsistente Ergebnisse.

Wenn Sie nur den aufgerufenen Dateinamen möchten, können Sie die File- basenameMethode mit einer der Variablen verwenden oder das Trennzeichen aufteilen und das letzte Element übernehmen.

$0und __FILE__haben einige kleine Unterschiede, aber für einzelne Skripte sind sie gleichwertig.

puts File.basename($0)

Es gibt einige Vorteile , die zu verwenden File.basename, File.extnameund File.dirnameSuite von Methoden. basenameNimmt einen optionalen Parameter, der die zu entfernende Erweiterung ist. Wenn Sie also nur den Basisnamen ohne die Erweiterung benötigen

File.basename($0, File.extname($0)) 

tut es, ohne das Rad neu zu erfinden oder sich mit variabler Länge oder fehlenden Verlängerungen oder der Möglichkeit des falschen Abschneidens von Verlängerungsketten " .rb.txt" befassen zu müssen, zum Beispiel:

ruby-1.9.2-p136 :004 > filename = '/path/to/file/name.ext'
 => "/path/to/file/name.ext" 
ruby-1.9.2-p136 :005 > File.basename(filename, File.extname(filename))
 => "name" 
ruby-1.9.2-p136 :006 > filename = '/path/to/file/name.ext' << '.txt'
 => "/path/to/file/name.ext.txt" 
ruby-1.9.2-p136 :007 > File.basename(filename, File.extname(filename))
 => "name.ext" 
der Blechmann
quelle
Danke für die sehr vollständige Antwort!
Adam_0
2
Beachten Sie, dass es ein anderes Verhalten gibt, wenn Sie die vorgeschlagenen Optionen in einem enthaltenen Skript mit 'require' oder 'require_relative verwenden. Mit $ 0 und $ PROGRAM_NAME wird der Name des aufrufenden Skripts zurückgegeben. Wenn Sie die Option DATEI mit umgebenden doppelten Unterbalken verwenden (Wie formatieren Sie das in einem Kommentar?), Wird der Name des enthaltenen Skripts zurückgegeben.
Mike663
18

Diese Antwort könnte etwas spät kommen, aber ich hatte das gleiche Problem und die akzeptierte Antwort schien mir nicht ganz zufriedenstellend zu sein, also habe ich etwas weiter nachgeforscht.

Was mich störte, war die Tatsache, dass die richtigen Informationen darüber, was der Benutzer eingegeben hatte, nicht wirklich vorhanden waren $0oder $PROGRAM_NAMEnicht . Wenn sich mein Ruby-Skript in einem PATH-Ordner befindet und der Benutzer den Namen der ausführbaren Datei eingibt (ohne Pfaddefinitionen wie oder ), wird es immer auf den Gesamtpfad erweitert../script/bin/script

Ich dachte, dies sei ein Ruby-Defizit, also versuchte ich dasselbe mit Python und zu meinem Leidwesen dort war es nicht anders.

Ein Freund schlug mir einen Hack vor, um nach dem real thingIn zu suchen /proc/self/cmdline, und das Ergebnis war: [ruby, /home/danyel/bin/myscript, arg1, arg2...](durch das Nullzeichen getrennt). Der Bösewicht hier ist execve(1)der, der den Pfad zum Gesamtpfad erweitert, wenn er an einen Dolmetscher weitergegeben wird.

Beispiel C Programm:

#include <stdlib.h>
#include <unistd.h>

extern char** environ;
int main() {
  char ** arr = malloc(10 * sizeof(char*));
  arr[0] = "myscript";
  arr[1] = "-h";
  arr[2] = NULL;
  execve("/home/danyel/bin/myscript", arr, environ);
}

Ausgabe: `Verwendung: / home / danyel / bin / myscript DATEI ...

Um zu beweisen, dass dies tatsächlich eine execveSache ist und nicht von Bash, können wir einen Dummy-Interpreter erstellen, der nichts anderes tut, als die an ihn übergebenen Argumente auszudrucken:

// interpreter.c
int main(int argc, const char ** argv) {
  while(*argv)
    printf("%s\n", *(argv++));
}

Wir kompilieren es und legen es in einem Pfadordner ab (oder legen den vollständigen Pfad nach dem Shebang ab) und erstellen ein Dummy-Skript in ~/bin/myscript/

#!/usr/bin/env interpreter
Hi there!

Nun, in unserer main.c:

#include <stdlib.h>

extern char** environ;
int main() {
  char ** arr = malloc(10 * sizeof(char*));
  arr[0] = "This will be totally ignored by execve.";
  arr[1] = "-v";
  arr[2] = "/var/log/apache2.log";
  arr[3] = NULL;
  execve("/home/danyel/bin/myscript", arr, environ);
}

Kompilieren und Ausführen ./main: Interpreter / home / danyel / bin / myscript -v /var/log/apache2.log

Der Grund dafür ist wahrscheinlich, dass , wenn das Skript in Ihrer PATH ist und der vollständige Pfad wurde nicht zur Verfügung gestellt, der Dolmetscher dies als erkennen würde No such fileFehler, der es tut , wenn Sie tun: ruby myrubyscript --options arg1und du bist nicht im Ordner mit diesem Skript .

Danyel
quelle
3

Verwenden Sie $0oder $PROGRAM_NAME, um den Dateinamen abzurufen, der gerade ausgeführt wird.

pierrotlefou
quelle
Dies gibt mir den vollständigen Weg. Ich möchte wissen, was der Benutzer eingegeben hat. Wenn ich zum Beispiel in meinem habe /usr/local/bin/myScriptund /usr/local/binin meinem $PATHbin und nur myScript/usr/local/bin/myScript$0
tippe
2
Wie wäre es $0.split("/").last?
Pierrotlefou
7
Ich frage nicht NUR nach dem Programmnamen. Ich meine, ich möchte genau das, was der Benutzer eingegeben hat, um das Programm auszuführen. Wenn sie tippen ./myScript, möchte ich eine Variable, die mir gibt ./myScript. Wenn sie tippten /usr/bin/local/myScript, möchte ich genau das. usw.
Adam_0
3

Dies ist keine Antwort auf Ihre Frage, aber es klingt, als würden Sie ein Rad neu erfinden. Schauen Sie sich die Optparse- Bibliothek an. Sie können damit Befehlszeilenschalter, Argumente usw. definieren und das ganze schwere Heben für Sie erledigen.

Chris Heald
quelle
2
Danke, aber ich brauche nichts so kompliziertes. Ich meine, ich habe nur zwei Argumente, also ist es ein zu einfacher Fall, um all das zu rechtfertigen.
Adam_0