$ 0 sempre incluirá o caminho para o script?

9

Eu quero usar o script atual para que eu possa imprimir as informações de ajuda e versão na seção de comentários no topo.

Eu estava pensando em algo assim:

grep '^#h ' -- "$0" | sed -e 's/#h //'

Mas então me perguntei o que aconteceria se o script estivesse localizado em um diretório que estivesse no PATH e fosse chamado sem especificar explicitamente o diretório.

Eu procurei por uma explicação das variáveis especiais e encontrei as seguintes descrições de $0 :

  • nome do shell ou programa atual

  • nome do arquivo do script atual

  • nome do próprio script

  • comando como foi executado

Nenhum deles deixa claro se o valor de $0 incluiria ou não o diretório se o script fosse invocado sem ele. O último, na verdade, implica para mim que não seria.

Teste no meu sistema (Bash 4.1)

Eu criei um arquivo executável em / usr / local / bin chamado scriptname com uma linha echo $0 e o invoquei de diferentes locais.

Estes são meus 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

Nesses testes, o valor de $0 é sempre exatamente como o script foi invocado, exceto se for chamado sem nenhum componente de caminho. Nesse caso, o valor de $0 é o caminho absoluto . Então, parece que seria seguro passar para outro comando.

Mas então me deparei com um comentário sobre o estouro de pilha que me confundiu. A resposta sugere usar $(dirname $0) para obter o diretório do script atual. O comentário (7 vezes acima) diz "que não funcionará se o script estiver no seu caminho".

Perguntas

  • Esse comentário está correto?
  • O comportamento é diferente em outros sistemas?
  • Existem situações em que $0 não incluiria o diretório?
por toxalot 16.03.2014 / 09:58

7 respostas

16

Nos casos mais comuns, $0 conterá um caminho, absoluto ou relativo ao script, então

script_path=$(readlink -e -- "$0")

(assumindo que há um comando readlink e ele suporta -e ) geralmente é uma maneira boa de obter o caminho absoluto canônico para o script.

$0 é atribuído a partir do argumento especificando o script como passado para o interpretador.

Por exemplo, em:

the-shell -shell-options the/script its args

$0 obtém the/script .

Quando você executa:

the/script its args

Seu shell fará um:

exec("the/script", ["the/script", "its", "args"])

Se o script contiver um #! /bin/sh - she-bang, por exemplo, o sistema transformará isso em:

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "the/script", "its", "args"])

(se não contiver um she-bang ou, mais geralmente, se o sistema retornar um erro ENOEXEC, então seu shell fará a mesma coisa)

Há uma exceção para scripts setuid / setgid em alguns sistemas, em que o sistema abrirá o script em algum fd x e será executado:

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "/dev/fd/x", "its", "args"])

para evitar condições de corrida (caso em que $0 conterá /dev/fd/x ).

Agora, você pode argumentar que /dev/fd/x é um caminho para esse script. No entanto, observe que, se você ler $0 , você quebrará o script enquanto consome a entrada.

Agora, há uma diferença se o nome do comando de script como invocado não contiver uma barra. Em:

the-script its args

Seu shell consultará the-script em $PATH . $PATH pode conter caminhos absolutos ou relativos (incluindo a cadeia vazia) para alguns diretórios. Por exemplo, se $PATH contiver /bin:/usr/bin: e the-script for encontrado no diretório atual, o shell fará o seguinte:

exec("the-script", ["the-script", "its", "args"])

que se tornará:

exec("/bin/sh", ["/bin/sh" or "the-script", "-", "the-script", "its", "args"]

Ou se for encontrado em /usr/bin :

exec("/usr/bin/the-script", ["the-script", "its", "args"])
exec("/bin/sh", ["/bin/sh" or "the-script" or "/usr/bin/the-script",
     "-", "/usr/bin/the-script", "its", "args")

Em todos os casos acima, exceto no caso do canto setuid, $0 conterá um caminho (absoluto ou relativo) para o script.

Agora, um script também pode ser chamado como:

the-interpreter the-script its args

Quando the-script como acima não contém caracteres de barra, o comportamento varia ligeiramente de shell para shell.

As implementações do AT & T ksh antigo estavam realmente procurando o script incondicionalmente em $PATH (que na verdade era um bug e uma falha de segurança para scripts setuid), então $0 na verdade não contém um caminho para o script, a menos que a pesquisa $PATH realmente tenha encontrado the-script no diretório atual.

O mais novo AT & T ksh tentaria interpretar the-script no diretório atual se for legível. Se não, procuraria por um legível e executável the-script em $PATH .

Para bash , verifica se the-script está no diretório atual (e não é um link simbólico danificado) e, caso contrário, procure por um legível (não necessariamente executável) the-script in $PATH .

zsh em sh emulação seria como bash , exceto que se the-script fosse um link simbólico quebrado no diretório atual, ele não procuraria por the-script em $PATH e, em vez disso, reportaria um erro.

Todos os outros shells parecidos com Bourne não parecem em the-script em $PATH .

Para todos os shells de qualquer maneira, se você achar que $0 não contém / e não é legível, provavelmente ele foi pesquisado em $PATH . Então, como os arquivos em $PATH provavelmente serão executáveis, provavelmente é uma aproximação segura usar command -v -- "$0" para encontrar seu caminho (embora isso não funcione se $0 também for o nome de um shell embutido ou palavra-chave (na maioria dos shells)).

Então, se você realmente quer cobrir esse caso, você pode escrever:

progname=$0
[ -r "$progname" ] || progname=$(
    PATH_terminated=${PATH-$(getconf PATH)}:
    IFS=:; set -f
    for i in $PATH_terminated; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

(aqui assumindo um shell POSIX onde o IFS é o delimitador de campo . Shells como zsh (em sh emulation) ou antigos pdksh -based que tratam o IFS como um separador de campo verá um elemento vazio extra na divisão de $PATH_terminated ).

Agora, existem maneiras mais esotéricas de invocar um script. Pode-se fazer:

the-shell < the-script

Ou:

cat the-script | the-shell

Nesse caso, $0 será o primeiro argumento ( argv[0] ) que o intérprete recebeu (acima de the-shell , mas pode ser qualquer coisa, geralmente o nome da base ou um caminho para esse intérprete).

Detectar que você está nessa situação com base no valor de $0 não é confiável. Você poderia olhar a saída de ps -o args= -p "$$" para obter uma pista. No caso de tubos, não há uma maneira real de voltar ao caminho para o script.

Também se pode fazer:

the-shell -c '. the-script' blah blih

Em seguida, exceto em zsh (e alguma implementação antiga do shell Bourne), $0 seria blah . Novamente, é difícil chegar ao caminho do script nesses shells.

Ou:

the-shell -c "$(cat the-script)" blah blih

etc.

Para garantir que você tenha o $progname correto, pesquise uma string específica, como:

progname=$0
[ -r "$progname" ] || progname=$(
    PATH_terminated=${PATH-$(getconf PATH)}:
    IFS=:; set -f
    for i in $PATH_terminated; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

[ -f "$progname" ] && grep -q 7YQLVVD3UIUDTA32LSE8U9UOHH < "$progname" ||
  progname=unknown

Mas, novamente, não acho que valha a pena o esforço.

    
por 16.03.2014 / 21:38
6

Aqui estão duas situações em que o diretório não será incluído:

> bash scriptname
scriptname

> bash <scriptname
bash

Em ambos os casos, o diretório atual teria que ser o diretório em que scriptname estava localizado.

No primeiro caso, o valor de $0 ainda pode ser passado para grep , uma vez que assume que o argumento FILE é relativo ao diretório atual.

No segundo caso, se as informações de ajuda e versão forem impressas apenas em resposta a uma opção de linha de comando específica, isso não deverá ser um problema. Não sei por que alguém invocaria um script desse modo para imprimir as informações de ajuda ou versão.

Advertências

  • Se o script alterar o diretório atual, você não desejaria usar caminhos relativos.

  • Se o script for originado, o valor de $0 geralmente será o script de chamada em vez do script de origem.

por 16.03.2014 / 09:58
3

Um argumento arbitrário zero pode ser especificado ao usar a opção -c na maioria dos shells (all?). Por exemplo:

sh -c 'echo $0' argv0

De man bash (escolhido puramente porque isso tem uma descrição melhor do que minha man sh - o uso é o mesmo, independentemente):

-c

If the -c option is present, then commands are read from the first non-option argument command_string. If there are arguments after the command_string, they are assigned to the positional parameters, starting with $0.

    
por 16.03.2014 / 12:04
3

NOTA: Outros já explicaram a mecânica de $0 , então vou pular tudo isso.

Eu geralmente passo a passo todo este problema e simplesmente uso o comando readlink -f $0 . Isso sempre lhe dará de volta o caminho completo do que você der a ele como argumento.

Exemplos

Digamos que estou aqui para começar:

$ pwd
/home/saml/tst/119929/adir

Crie um diretório + arquivo:

$ mkdir adir
$ touch afile
$ cd adir/

Agora comece a exibir readlink :

$ readlink -f ../adir
/home/saml/tst/119929/adir

$ readlink -f ../
/home/saml/tst/119929

$ readlink -f ../afile 
/home/saml/tst/119929/afile

$ readlink -f .
/home/saml/tst/119929/adir

Trapaça adicional

Agora, com um resultado consistente retornado quando interrogamos $0 via readlink , podemos usar simplesmente dirname $(readlink -f $0) para obter o caminho absoluto para o script -or- basename $(readlink -f $0) para obter o nome real do script .

    
por 16.03.2014 / 15:06
0

Minha página man diz:

$0 : expands to the name of shell or shell script.

Parece que isso se traduz em argv[0] do shell atual - ou no primeiro argumento de linha de comando nonoperand que o interpretador atual é alimentado quando invocado. Afirmei anteriormente que sh ./somescript ativaria a $0 $ENV variável para sh , mas isso estava incorreto porque o shell é um novo processo próprio e chamado com um novo $ENV .

Desta forma, sh ./somescript.sh difere de . ./somescript.sh que é executado no ambiente atual e $0 já está definido.

Você pode verificar isso comparando $0 a /proc/$$/status .

echo 'script="/proc/$$/status"
    echo $0
    cat "$script"' \
    > ./script.sh
sh ./script.sh ; . ./script.sh

Obrigado pela correção, @toxalot. Eu aprendi alguma coisa.

    
por 16.03.2014 / 11:13
0

I want to grep the current script so I can print out help and version information from the comments section at the top.

Enquanto $0 contém o nome do script que pode conter um caminho prefixado com base na maneira como o script é chamado, sempre usei ${0##*/} para imprimir o nome do script na saída da ajuda que remove qualquer caminho inicial de $0 .

Extraído de Guia avançado de scripts de bash - Seção 10.2 substituição de parâmetro

${var#Pattern}

Remove from $var the shortest part of $Pattern that matches the front end of $var.

${var##Pattern}

Remove from $var the longest part of $Pattern that matches the front end of $var.

Portanto, a parte mais longa de $0 que corresponde a */ será o prefixo do caminho inteiro, retornando apenas o nome do script.

    
por 16.03.2014 / 14:21
0

Para algo semelhante eu uso:

rPath="$(dirname $(realpath $0))"
echo $rPath 

rPath=$(dirname $(readlink -e -- "$0"))
echo $rPath 

rPath sempre tem o mesmo valor.

    
por 27.09.2015 / 04:05