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?
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"
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.
Utilisation $0
ou $PROGRAM_NAME
pour obtenir le nom du fichier en cours d'exécution.
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.