Je veux grep le script actuel afin que je puisse imprimer les informations d'aide et de version à partir de la section des commentaires en haut.
Je pensais à quelque chose comme ça:
grep '^#h ' -- "$0" | sed -e 's/#h //'
Mais ensuite je me suis demandé ce qui se passerait si le script se trouvait dans un répertoire qui était dans PATH et appelé sans spécifier explicitement le répertoire.
J'ai cherché une explication des variables spéciales et j'ai trouvé les descriptions suivantes de $0
:
nom du shell ou du programme en cours
nom de fichier du script actuel
nom du script lui-même
commande telle qu'elle a été exécutée
Aucun de ceux-ci ne précise si la valeur de $0
inclurait ou non le répertoire si le script était invoqué sans lui. Le dernier m'implique en fait que non.
Test sur mon système (Bash 4.1)
J'ai créé un fichier exécutable dans / usr / local / bin appelé scriptname avec une ligne echo $0
et l' ai invoqué à partir de différents emplacements.
Voici mes résultats:
> cd /usr/local/bin/test
> ../scriptname
../scriptname
> cd /usr/local/bin
> ./scriptname
./scriptname
> cd /usr/local
> bin/scriptname
bin/scriptname
> cd /tmp
> /usr/local/bin/scriptname
/usr/local/bin/scriptname
> scriptname
/usr/local/bin/scriptname
Dans ces tests, la valeur de $0
est toujours exactement la façon dont le script a été appelé, sauf s'il est appelé sans aucun composant de chemin. Dans ce cas, la valeur de $0
est le chemin absolu . Il semble donc qu'il serait prudent de passer à une autre commande.
Mais je suis tombé sur un commentaire sur Stack Overflow qui m'a dérouté. La réponse suggère d'utiliser $(dirname $0)
pour obtenir le répertoire du script actuel. Le commentaire (voté 7 fois) dit "cela ne fonctionnera pas si le script est sur votre chemin".
Des questions
- Ce commentaire est-il correct?
- Le comportement est-il différent sur d'autres systèmes?
- Y a-t-il des situations où
$0
le répertoire ne serait pas inclus?
la source
$0
il y a autre chose que le script, qui répond au titre de la question. Cependant, je suis également intéressé par les situations où se$0
trouve le script lui-même, mais n'inclut pas le répertoire. En particulier, j'essaie de comprendre le commentaire fait sur la réponse SO.Réponses:
Dans les cas les plus courants,
$0
contiendra un chemin, absolu ou relatif au script, donc(en supposant qu'il existe une
readlink
commande et qu'elle prend en charge-e
) est généralement un assez bon moyen d'obtenir le chemin absolu canonique du script.$0
est attribué à partir de l'argument spécifiant le script tel qu'il a été transmis à l'interpréteur.Par exemple, dans:
$0
obtientthe/script
.Lorsque vous exécutez:
Votre shell fera:
Si le script contient un
#! /bin/sh -
she-bang par exemple, le système le transformera en:(si elle ne contient pas de coup sec, ou plus généralement si le système retourne une erreur ENOEXEC, alors c'est votre shell qui fera la même chose)
Il existe une exception pour les scripts setuid / setgid sur certains systèmes, où le système ouvrira le script sur certains
fd
x
et s'exécutera à la place:pour éviter les conditions de course (auquel cas
$0
contiendra/dev/fd/x
).Maintenant, vous pouvez dire que
/dev/fd/x
c'est un chemin vers ce script. Notez cependant que si vous lisez$0
, vous romprez le script lorsque vous consommerez l'entrée.Maintenant, il y a une différence si le nom de la commande de script invoqué ne contient pas de barre oblique. Dans:
Votre shell va chercher
the-script
dans$PATH
.$PATH
peut contenir des chemins absolus ou relatifs (y compris la chaîne vide) vers certains répertoires. Par exemple, si$PATH
contient/bin:/usr/bin:
etthe-script
se trouve dans le répertoire courant, le shell fera:qui deviendra:
Ou s'il se trouve dans
/usr/bin
:Dans tous les cas ci-dessus, à l'exception du cas du coin setuid,
$0
contiendra un chemin (absolu ou relatif) vers le script.Maintenant, un script peut également être appelé comme:
Lorsque,
the-script
comme ci-dessus, ne contient pas de barres obliques, le comportement varie légèrement d'un shell à l'autre.Les anciennes
ksh
implémentations AT&T recherchaient en fait le script sans condition$PATH
(ce qui était en fait un bogue et une faille de sécurité pour les scripts setuid), donc$0
ne contenaient en fait pas de chemin vers le script à moins que la$PATH
recherche ne se trouve réellementthe-script
dans le répertoire actuel.Un AT&T plus récent
ksh
essaierait d'interpréterthe-script
dans le répertoire courant s'il est lisible. Sinon, il rechercherait un fichier lisible et exécutablethe-script
dans$PATH
.Pour
bash
, il vérifie s'il sethe-script
trouve dans le répertoire courant (et n'est pas un lien symbolique cassé) et sinon, recherche un fichier lisible (pas nécessairement exécutable)the-script
dans$PATH
.zsh
ensh
émulation ferait comme ,bash
sauf que sithe-script
est un lien symbolique cassé dans le répertoire courant, il ne serait pas la recherche d'unthe-script
dans$PATH
et serait plutôt une erreur.Tous les autres Bourne comme des obus ne regardent pas
the-script
dans$PATH
.Pour tous ces shells de toute façon, si vous trouvez qu'il
$0
ne contient pas de/
et n'est pas lisible, alors il a probablement été recherché$PATH
. Ensuite, comme les fichiers$PATH
sont susceptibles d'être exécutables, c'est probablement une approximation sûre à utilisercommand -v -- "$0"
pour trouver son chemin (bien que cela ne fonctionnerait pas s'il$0
se trouve que c'est aussi le nom d'un shell intégré ou d'un mot clé (dans la plupart des shells)).Donc, si vous voulez vraiment couvrir ce cas, vous pouvez l'écrire:
(le
""
annexé à$PATH
est de conserver un élément vide de fin avec des coques dont$IFS
le délimiteur au lieu de séparateur agit ).Maintenant, il existe des moyens plus ésotériques d'invoquer un script. On pourrait faire:
Ou:
Dans ce cas,
$0
sera le premier argument (argv[0]
) que l' interprète a reçu (cithe-shell
- dessus , mais cela peut être n'importe quoi, mais généralement le nom de base ou un chemin d'accès à cet interprète).Détecter que vous vous trouvez dans cette situation en fonction de la valeur de
$0
n'est pas fiable. Vous pouvez regarder la sortie deps -o args= -p "$$"
pour obtenir un indice. Dans le cas du tuyau, il n'y a aucun moyen réel de revenir à un chemin d'accès au script.On pourrait aussi faire:
Ensuite, sauf dans
zsh
(et une ancienne implémentation du shell Bourne), ce$0
seraitblah
. Encore une fois, difficile d'accéder au chemin du script dans ces coquilles.Ou:
etc.
Pour vous assurer que vous avez le droit
$progname
, vous pouvez rechercher une chaîne spécifique comme:Mais encore une fois, je ne pense pas que cela en vaille la peine.
la source
"-"
dans les exemples ci-dessus. D'après mon expérience,exec("the-script", ["the-script", "its", "args"])
devientexec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"])
, avec bien sûr la possibilité d'une option interprète.#! /bin/sh -
est le "toujours utilisercmd -- something
si vous ne pouvez pas garantir quesomething
cela ne commencera pas par-
" l' adage de bonne pratique appliqué ici/bin/sh
(où-
le marqueur de fin d'option est plus portable que--
) avecsomething
étant le chemin / nom du scénario. Si vous ne l'utilisez pas pour les scripts setuid (sur les systèmes qui les prennent en charge mais pas avec la méthode / dev / fd / x mentionnée dans la réponse), alors on peut obtenir un shell racine en créant un lien symbolique vers votre script appelé-i
ou-s
pour exemple./bin/sh
devrait désactiver son propre traitement d'options s'il peut détecter qu'il exécute setuid. Je ne vois pas comment l'utilisation de / dev / fd / x en soi résout ce problème. Vous avez toujours besoin du tiret simple / double, je pense./dev/fd/x
commence par/
, non-
. Le but principal est de supprimer la condition de concurrence entre les deuxexecve()
si (entre leexecve("the-script")
qui élève les privilèges et le suivantexecve("interpreter", "thescript")
oùinterpreter
ouvre le script plus tard (qui peut très bien avoir été remplacé par un lien symbolique vers quelque chose d'autre entre-temps). les scripts suid font correctement uneexecve("interpreter", "/dev/fd/n")
place où n a été ouvert dans le cadre du premier execve ().Voici deux situations où le répertoire ne serait pas inclus:
Dans les deux cas, le répertoire actuel devrait être le répertoire où se trouvait le nom du script .
Dans le premier cas, la valeur de
$0
pourrait toujours être transmisegrep
car elle suppose que l'argument FILE est relatif au répertoire en cours.Dans le second cas, si les informations d'aide et de version ne sont imprimées qu'en réponse à une option de ligne de commande spécifique, cela ne devrait pas poser de problème. Je ne sais pas pourquoi quelqu'un invoquerait un script de cette façon pour imprimer l'aide ou la version.
Avertissements
Si le script modifie le répertoire actuel, vous ne voudriez pas utiliser de chemins relatifs.
Si le script provient, la valeur de
$0
sera généralement le script appelant plutôt que le script source.la source
Un argument arbitraire zéro peut être spécifié lors de l'utilisation de l'
-c
option pour la plupart (tous?) Des shells. Par exemple:De
man bash
(choisi uniquement parce que cela a une meilleure description que monman sh
- l'utilisation est la même indépendamment):la source
argv0
c'est son premier argument de ligne de commande non opérande .REMARQUE: d' autres ont déjà expliqué la mécanique de
$0
donc je vais sauter tout cela.En général, je contourne ce problème et j'utilise simplement la commande
readlink -f $0
. Cela vous donnera toujours le chemin complet de tout ce que vous lui donnez comme argument.Exemples
Disons que je suis ici pour commencer:
Créez un répertoire + fichier:
Maintenant, commencez à vous montrer
readlink
:Ruse supplémentaire
Maintenant, avec un résultat cohérent renvoyé lorsque nous interrogeons
$0
via,readlink
nous pouvons utiliser simplementdirname $(readlink -f $0)
pour obtenir le chemin absolu vers le script -ou-basename $(readlink -f $0)
pour obtenir le nom réel du script.la source
Ma
man
page dit:Il semble que cela se traduise par
argv[0]
le shell actuel - ou le premier argument de ligne de commande non opérande auquel le shell en cours d'interprétation est alimenté lorsqu'il est appelé. J'ai déjà indiqué quesh ./somescript
cela acheminerait sa variable vers mais c'était incorrect car c'est un nouveau processus qui lui est propre et invoqué avec un nouveau .$0 $ENV
sh
shell
$ENV
De cette façon,
sh ./somescript.sh
diffère de celui. ./somescript.sh
qui s'exécute dans l' environnement actuel et$0
est déjà défini.Vous pouvez vérifier cela en comparant
$0
à/proc/$$/status
.Merci pour la correction, @toxalot. J'ai appris quelque chose.
la source
. ./myscript.sh
ousource ./myscript.sh
),$0
c'est le shell. Mais s'il est passé en argument au shell (sh ./myscript.sh
), alors$0
c'est le chemin du script. Bien sûr,sh
sur mon système est Bash. Je ne sais donc pas si cela fait une différence ou non.exec
elle ne devrait pas et au lieu de cela, mais avec elle, elle devraitexec
../somescript
exécutez avec la bangline#!/bin/sh
, cela équivaudrait à courir/bin/sh ./somescript
. Sinon, ils ne font aucune différence pour le shell.Bien qu'il
$0
contienne le nom du script, il peut contenir un chemin préfixé basé sur la façon dont le script est appelé, j'ai toujours utilisé${0##*/}
pour imprimer le nom du script dans la sortie de l'aide qui supprime tout chemin principal$0
.Tiré du guide Advanced Bash Scripting - Section 10.2 Substitution de paramètres
Ainsi, la partie la plus longue de
$0
ces correspondances*/
sera le préfixe de chemin entier, ne renvoyant que le nom du script.la source
Pour quelque chose de similaire, j'utilise:
rPath
a toujours la même valeur.la source