web-dev-qa-db-fra.com

Comment puis-je obtenir le nom de la commande appelée pour les invites d'utilisation dans Ruby?

J'ai écrit un joli petit Ruby script il y a quelque temps que j'aime plutôt. Je voudrais améliorer sa robustesse en vérifiant le bon nombre d'arguments:

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

Bien sûr, c'est un pseudocode. Quoi qu'il en soit, en C ou C++, je pourrais utiliser argv[0] pour obtenir le nom utilisé par l'utilisateur pour accéder à ma commande, qu'il l'appelle comme ./myScript.rb ou myScript.rb ou /usr/local/bin/myScript.rb. En Ruby, je sais que ARGV[0] est le premier vrai argument et ARGV ne contient pas le nom de la commande. Y a-t-il un moyen de l'obtenir?

75
adam_0

Ruby a trois façons de nous donner le nom du script appelé:

#!/usr/bin/env Ruby

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

L'enregistrement de ce code en tant que "test.rb" et son appel de plusieurs façons montre que le script reçoit le nom tel qu'il lui a été transmis par le système d'exploitation. Un script ne sait que ce que l'OS lui dit:

$ ./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

L'appeler en utilisant le ~ le raccourci pour $ HOME dans le deuxième exemple montre le système d'exploitation le remplaçant par le chemin développé, correspondant à ce qui est dans le troisième exemple. Dans tous les cas, c'est ce que le système d'exploitation a adopté.

La liaison au fichier à l'aide de liens physiques et de liens logiciels montre un comportement cohérent. J'ai créé un lien dur pour test1.rb et un lien logiciel pour test2.rb:

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

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

Lancement Ruby test.rb avec l'une des variantes du nom du script renvoie des résultats cohérents.

Si vous ne voulez que le nom de fichier appelé, vous pouvez utiliser la méthode basename de File avec l'une des variables ou diviser sur le délimiteur et prendre le dernier élément.

$0 et __FILE__ ont quelques différences mineures mais pour les scripts simples, ils sont équivalents.

puts File.basename($0)

Ajout mineur:

Il y a certains avantages à utiliser le File.basename, File.extname et File.dirname suite de méthodes. basename prend un paramètre facultatif, qui est l'extension à supprimer, donc si vous avez juste besoin du nom de base sans l'extension

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

le fait sans réinventer la roue ou avoir à faire face à des extensions de longueur variable ou manquantes ou la possibilité de tronquer les chaînes d'extension de manière incorrecte ".rb.txt" par exemple:

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" 
140
the Tin Man

cette réponse pourrait arriver un peu tard, mais j'ai eu le même problème et la réponse acceptée ne m'a pas semblé très satisfaisante, j'ai donc enquêté un peu plus.

Ce qui m'a dérangé, c'est le fait que $0 Ou $PROGRAM_NAME Ne contenaient pas vraiment les informations correctes sur ce que l'utilisateur avait tapé. Si mon Ruby se trouvait dans un dossier PATH et que l'utilisateur a entré le nom de l'exécutable (sans définition de chemin tel que ./script Ou /bin/script), Il le ferait toujours développer le chemin total.

Je pensais que c'était un Ruby deficite, donc j'ai essayé la même chose avec Python et à mon grand regret là-bas, ce n'était pas différent.

Un ami m'a suggéré un hack pour rechercher le real thing Dans /proc/self/cmdline, Et le résultat a été: [Ruby, /home/danyel/bin/myscript, arg1, arg2...] (Séparé par le caractère nul). Le méchant ici est execve(1) qui étend le chemin vers le chemin total quand il le passe à un interprète.

Exemple de programme C:

#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);
}

Sortie: `Utilisation:/home/danyel/bin/myscript FILE ...

Pour prouver qu'il s'agit bien d'une chose execve et non de bash, nous pouvons créer un interpréteur factice qui ne fait qu'imprimer les arguments qui lui sont passés:

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

Nous le compilons et le mettons dans un dossier de chemin (ou mettons le chemin complet après le Shebang) et créons un script factice dans ~/bin/myscript/

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

Maintenant, dans notre 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);
}

Compilation et exécution ./main: Interpreter/home/danyel/bin/myscript -v /var/log/Apache2.log

La raison derrière cela est très probablement que si le script est dans votre PATH et que le chemin complet était pas fourni, l'interpréteur reconnaîtrait cela comme une erreur No such file, Ce qu'il fait si vous faire: Ruby myrubyscript --options arg1 et vous n'êtes pas dans le dossier avec ce script.

16
Danyel

Utilisation $0ou $PROGRAM_NAME pour obtenir le nom du fichier en cours d'exécution.

3
pierrotlefou

Ce n'est pas tout à fait une réponse à votre question, mais il semble que vous réinventiez une roue. Regardez la bibliothèque optparse . Il vous permet de définir des commutateurs de ligne de commande, des arguments, etc., et il fera tout le travail lourd pour vous.

2
Chris Heald