Quiero obtener el script actual para poder imprimir la ayuda y la información de la versión desde la sección de comentarios en la parte superior.
Estaba pensando en algo como esto:
grep '^#h ' -- "$0" | sed -e 's/#h //'
Pero luego me pregunté qué pasaría si el script se ubicara en un directorio que estaba en PATH y se llamara sin especificar explícitamente el directorio.
Busqué una explicación de las variables especiales y encontré las siguientes descripciones de $0
:
nombre del shell o programa actual
nombre de archivo del script actual
nombre del guión en sí
comando como se ejecutó
Ninguno de estos aclara si el valor de $0
incluiría o no el directorio si se invocara el script sin él. El último realmente me implica que no lo haría.
Prueba en mi sistema (Bash 4.1)
Creé un archivo ejecutable en / usr / local / bin llamado scriptname con una línea echo $0
y lo invoqué desde diferentes ubicaciones.
Estos son mis resultados:
> 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
En estas pruebas, el valor de $0
siempre es exactamente cómo se invocó el script, excepto si se invoca sin ningún componente de ruta. En ese caso, el valor de $0
es la ruta absoluta . Entonces parece que sería seguro pasar a otro comando.
Pero luego encontré un comentario sobre Stack Overflow que me confundió. La respuesta sugiere usar $(dirname $0)
para obtener el directorio del script actual. El comentario (votado 7 veces) dice "eso no funcionará si el script está en su camino".
Preguntas
- ¿Es correcto ese comentario?
- ¿Es el comportamiento diferente en otros sistemas?
- ¿Hay situaciones en las
$0
que no incluiría el directorio?
fuente
$0
hay algo más que el guión, que responde el título de la pregunta. Sin embargo, también estoy interesado en situaciones en las que se$0
encuentra el script en sí, pero no incluye el directorio. En particular, estoy tratando de entender el comentario hecho sobre la respuesta SO.Respuestas:
En los casos más comunes,
$0
contendrá una ruta, absoluta o relativa a la secuencia de comandos, por lo que(suponiendo que haya un
readlink
comando y lo admita-e
) generalmente es una forma suficientemente buena de obtener la ruta de acceso absoluta canónica al script.$0
se asigna a partir del argumento que especifica el guión que se pasa al intérprete.Por ejemplo, en:
$0
consiguethe/script
.Cuando corres:
Su caparazón hará un:
Si el script contiene un
#! /bin/sh -
she-bang, por ejemplo, el sistema lo transformará a:(si no contiene un she-bang, o más generalmente si el sistema devuelve un error ENOEXEC, entonces es su shell el que hará lo mismo)
Hay una excepción para los scripts setuid / setgid en algunos sistemas, donde el sistema abrirá el script en algunos
fd
x
y se ejecutará en su lugar:para evitar condiciones de carrera (en cuyo caso
$0
contendrá/dev/fd/x
).Ahora, puede argumentar que
/dev/fd/x
es un camino hacia ese script. Sin embargo$0
, tenga en cuenta que si lee , romperá el script a medida que consume la entrada.Ahora, hay una diferencia si el nombre del comando de script como invocado no contiene una barra inclinada. En:
Su cáscara buscará
the-script
en$PATH
.$PATH
puede contener rutas absolutas o relativas (incluida la cadena vacía) a algunos directorios. Por ejemplo, si$PATH
contiene/bin:/usr/bin:
ythe-script
se encuentra en el directorio actual, el shell hará un:que se convertirá en:
O si se encuentra en
/usr/bin
:En todos los casos anteriores, excepto el caso de la esquina setuid,
$0
contendrá una ruta (absoluta o relativa) al script.Ahora, un script también se puede llamar como:
Cuando
the-script
como arriba no contiene caracteres de barra diagonal, el comportamiento varía ligeramente de un shell a otro.Las
ksh
implementaciones antiguas de AT&T realmente buscaban el script incondicionalmente$PATH
(lo que en realidad era un error y un agujero de seguridad para los scripts setuid), por lo que en$0
realidad no contenía una ruta al script a menos que la$PATH
búsqueda realmente se encontrarathe-script
en el directorio actual.Los nuevos AT&T
ksh
intentarían interpretarthe-script
en el directorio actual si es legible. Si no, buscaría un archivo legible y ejecutablethe-script
en$PATH
.Para
bash
, comprueba sithe-script
está en el directorio actual (y no es un enlace simbólico roto) y si no, las operaciones de búsqueda para un legible (no necesariamente ejecutable)the-script
en$PATH
.zsh
ensh
la emulación como lo haríabash
excepto que sithe-script
es un enlace simbólico roto en el directorio actual, no sería buscar unthe-script
en$PATH
y en su lugar informar de un error.Todos los otros proyectiles tipo Bourne no miran
the-script
hacia adentro$PATH
.De todos modos, para todos esos proyectiles, si encuentra que
$0
no contiene/
ay no es legible, entonces probablemente se haya buscado$PATH
. Luego, dado que$PATH
es probable que los archivos sean ejecutables, es probable que sea una aproximación segura de usarcommand -v -- "$0"
para encontrar su ruta (aunque eso no funcionaría si$0
también fuera el nombre de un shell incorporado o una palabra clave (en la mayoría de los shells)).Entonces, si realmente quieres cubrir ese caso, puedes escribirlo:
(lo que se
""
adjunta$PATH
es para preservar un elemento vacío final con conchas que$IFS
actúa como delimitador en lugar de separador ).Ahora, hay formas más esotéricas de invocar un script. Se podría hacer:
O:
En ese caso,
$0
será el primer argumento (argv[0]
) que recibió el intérprete (arribathe-shell
, pero eso podría ser cualquier cosa, aunque generalmente sea el nombre base o una ruta a ese intérprete).Detectar que estás en esa situación en función del valor de
$0
no es confiable. Podrías mirar la salida deps -o args= -p "$$"
para obtener una pista. En el caso de la tubería, no hay una forma real de volver a la ruta del script.También se podría hacer:
Entonces, excepto en
zsh
(y alguna implementación antigua del shell Bourne),$0
seríablah
. Nuevamente, es difícil llegar al camino del guión en esos shells.O:
etc.
Para asegurarse de que tiene el derecho
$progname
, puede buscar una cadena específica como:Pero de nuevo, no creo que valga la pena el esfuerzo.
fuente
"-"
en los ejemplos anteriores. En mi experiencia, seexec("the-script", ["the-script", "its", "args"])
convierteexec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"])
, por supuesto, en la posibilidad de una opción de intérprete.#! /bin/sh -
es el "usocmd -- something
siempre que no pueda garantizar quesomething
no comenzará con-
" el adagio de buenas prácticas aquí aplicado/bin/sh
(donde-
el marcador de fin de opción es más portátil que--
) consomething
ser la ruta / nombre del guión. Si no usa eso para scripts setuid (en sistemas que los admiten pero no con el método / dev / fd / x mencionado en la respuesta), entonces puede obtener un shell raíz creando un enlace simbólico a su script llamado-i
o-s
para ejemplo./bin/sh
debería deshabilitar su propio procesamiento de opciones si puede detectar que se está ejecutando setuid. No veo cómo usar / dev / fd / x por sí solo soluciona eso. Todavía necesitas el guión simple / doble, creo./dev/fd/x
comienza con/
, no-
. Sinexecve()
embargo, el objetivo principal es eliminar la condición de carrera entre los dos s (entre elexecve("the-script")
cual eleva los privilegios y el posteriorexecve("interpreter", "thescript")
dondeinterpreter
abre el script más tarde (que bien podría haber sido reemplazado por un enlace simbólico a otra cosa mientras tanto). Sistemas que implementan los scripts de suid hacen correctamente unexecve("interpreter", "/dev/fd/n")
lugar donde se ha abierto n como parte del primer execve ().Aquí hay dos situaciones en las que el directorio no se incluiría:
En ambos casos, el directorio actual tendría que ser el directorio donde se encontraba el nombre del script .
En el primer caso, el valor de
$0
todavía podría pasarsegrep
ya que supone que el argumento FILE es relativo al directorio actual.En el segundo caso, si la información de ayuda y versión solo se imprime en respuesta a una opción de línea de comando específica, no debería ser un problema. No estoy seguro de por qué alguien invocaría un script de esa manera para imprimir la ayuda o la información de la versión.
Advertencias
Si el script cambia el directorio actual, no querrá usar rutas relativas.
Si el script tiene su origen, el valor de
$0
usualmente será el script de la persona que llama en lugar del script de origen.fuente
Se puede especificar un argumento arbitrario cero cuando se usa la
-c
opción para la mayoría de los shells (¿todos?). P.ej:De
man bash
(elegido únicamente porque tiene una mejor descripción que miman sh
: el uso es el mismo independientemente):fuente
argv0
es su primer argumento no operativo y de línea de comandos.NOTA: Otros ya han explicado la mecánica de,
$0
así que me saltearé todo eso.Por lo general, paso por alto todo este problema y simplemente uso el comando
readlink -f $0
. Esto siempre te devolverá el camino completo de lo que sea que le des como argumento.Ejemplos
Digamos que estoy aquí para empezar:
Hacer un directorio + archivo:
Ahora comienza a presumir
readlink
:Trucos adicionales
Ahora, con un resultado coherente que se devuelve cuando interrogamos a
$0
través dereadlink
, podemos usar simplementedirname $(readlink -f $0)
para obtener la ruta absoluta al script, obasename $(readlink -f $0)
para obtener el nombre real del script.fuente
Mi
man
página dice:Parece que esto se traduce en
argv[0]
el shell actual, o en el primer argumento de línea de comandos no operativo y al que se alimenta el shell de interpretación actual cuando se invoca. He dicho anteriormente que elsh ./somescript
camino sería la variable de a , pero esto era incorrecto porque el es un nuevo proceso de su propia e invocado por una nueva .$0 $ENV
sh
shell
$ENV
De esta manera
sh ./somescript.sh
difiere de lo. ./somescript.sh
que se ejecuta en el entorno actual y$0
ya está configurado.Puede verificar esto comparando
$0
con/proc/$$/status
.Gracias por la corrección, @toxalot. Aprendí algo
fuente
. ./myscript.sh
osource ./myscript.sh
), entonces$0
es el shell. Pero si se pasa como argumento al shell (sh ./myscript.sh
), entonces$0
es la ruta al script. Por supuesto,sh
en mi sistema está Bash. Entonces no sé si eso hace la diferencia o no.exec
y, en su lugar, buscarlo, pero con él, deberíaexec
serlo../somescript
ejecuta con el punto de referencia#!/bin/sh
, sería equivalente a correr/bin/sh ./somescript
. De lo contrario, no hacen ninguna diferencia en el caparazón.Si bien
$0
contiene el nombre del script, puede contener una ruta prefijada basada en la forma en que se llama el script, siempre he usado${0##*/}
para imprimir el nombre del script en la salida de ayuda que elimina cualquier ruta principal$0
.Tomado de la guía avanzada de secuencias de comandos Bash - Sección 10.2 sustitución de parámetros
Entonces, la parte más larga de
$0
esas coincidencias*/
será el prefijo de ruta completo, devolviendo solo el nombre del script.fuente
Para algo similar uso:
rPath
Siempre tiene el mismo valor.fuente