Identificar com segurança o caminho para o diretório que contém o script em execução

2

Existem muitas respostas no SO para responder a essa pergunta:

E há algumas perguntas semelhantes no Unix & Linux para scripts sourced .

Ainda assim, alguns deles são específicos do Bash (eles dependem do $BASH_SOURCE ). Alguns deles não funcionam se o nome do diretório contiver uma nova linha, e alguns deles não funcionam bem se houver são links simbólicos envolvidos.

O que é uma maneira confiável de obter o caminho para o diretório que contém o script de execução / chamada em cada shell principal? Ter uma solução que verifique cada tipo de shell está bem.

    
por Amelio Vazquez-Reina 12.05.2015 / 14:45

2 respostas

1

Existem heurísticas que podem ajudá-lo, mas não há uma maneira totalmente confiável.

Otheus mostra como usar descritores de arquivos . Essa é uma boa heurística, que funciona na maioria dos casos. No entanto, há casos extremos em que ele falha e não há como detectar falhas.

Exemplo: pegue o seguinte script.

#!/bin/sh
set
lsof -p$$ | sed 's/[0-9][0-9]*//'

Faça duas cópias do script, uma chamada foo , uma chamada bar . Agora vamos enfatizar isso um pouco:

$ env -i PATH=/bin:/usr/bin perl -MPOSIX -e 'dup2(4, 11) or die $!; exec "dash", "foo", "bar"' 3<foo 4<bar </dev/null >foo.out
$ env -i PATH=/bin:/usr/bin perl -MPOSIX -e 'dup2(3, 10) or die $!; exec "dash", "bar", "foo"' 3<foo 4<bar </dev/null >bar.out
$ diff foo.out bar.out

17c17     < traço gilles 1w REG 0,24 99 10130024 /tmp/202954/foo.out     ---

dash gilles 1w REG 0,24 99 10130022 /tmp/202954/bar.out

A única diferença aqui é o arquivo no qual registrei a saída.

Outro caso em que essa heurística falharia seria se o shell fosse chamado na entrada padrão ou com -c .

Outra abordagem é analisar a linha de comando do shell. No Linux, você pode acessá-lo através de /proc . Os argumentos são delimitados por nulos, o que é difícil de analisar com ferramentas de shell portáveis (ferramentas GNU recentes tornam isso mais fácil), mas isso pode ser feito. Portavelmente, você precisa chamar ps para acessar os argumentos, e não há nenhum formato de saída que não seja ambíguo: ps -o args concatena os argumentos com espaços e pode ser truncado.

Mesmo no Linux, o problema que você encontrará aqui é que o shell pode ter sido chamado com opções desconhecidas pelo seu script, e uma dessas opções pode levar um argumento. Por exemplo, suponha que você tenha um script chamado 1 e outro script chamado 2 .

mksh -T 1 2

Isso invoca o mksh em /dev/tty1 e executa o script 2 .

zsh -T 1 2

Isso chama zsh com a opção cprecedences e executa o script 1 com o argumento 2 .

Você precisa ter conhecimento de conchas individuais para diferenciá-las. Com esse método, você pode detectar casos extremos: se você vir uma opção fora do padrão, salve.

    
por 13.05.2015 / 02:17
3

Eu vou fazer uma primeira tentativa com isso. Alguém mais irá melhorar.

Antes de executar seu script, o shell abrirá um descritor de arquivo para o arquivo. Normalmente, isso é atribuído em fd 255. De qualquer forma, se houver um fd aberto, então lsof poderá encontrá-lo. Portanto, usamos lsof -p $$ e obtemos o nome do arquivo do descritor de arquivo mais alto. lsof não funciona com todos os tipos de unix. O wiki para o BSD diz que o equivalente existe fstat . Parece estar em Darwin (Mac OS). Com '-F

Um exemplo de script:

#!/bin/sh

this_script_path='lsof -p $$  | awk '/\/'${0##*/}'$/' | cut -c 55-'

Obviamente, o corte é muito dependente da formatação específica de lsof. Podemos aliviar isso na versão 2. BTW: Minha versão de lsof traduz caracteres não imprimíveis para que até mesmo guias em nomes de caminho sejam convertidas em \t .

Versão 2 . Desculpas em avançado para o código perl feio. Desta vez, vamos usar a opção -F para controlar a saída. Com -F fn , obteremos a saída assim:

p3834
fcwd
n/home/joe/test
frtd
n/
ftxt
n/bin/bash
fmem
n/lib64/ld-2.12.so
fmem
n/lib64/libdl-2.12.so
fmem
n/lib64/libc-2.12.so
fmem
n/lib64/libtinfo.so.5.7
fmem
n/usr/lib/locale/locale-archive
fmem
n/usr/lib64/gconv/gconv-modules.cache
f0
n/dev/pts/1
f1
n/dev/pts/1
f2
n/dev/pts/1
f255
n/home/joe/test/t.sh

Precisamos converter essa bagunça para que o mais alto descritor de arquivo (suponho que você não pode confiar em 255) é o nome do script. (Isso parece funcionar em dash também.)

this_script_path='lsof -p $$ -F fn | 
  perl -lane '
        $fd=$1,next if /^f(\d+)/; 
        $p{$fd}=$1 if $fd and /^n(.*)/;
        $fd="";
  }END { 
        @x=sort {$a<=>$b} keys %p;
        print $p{$x[-1]}; 
  }{''

O script perl é feio, eu concordo. Foi um one-liner que eu terminei por clareza. Capturamos o número do descritor de arquivo se a linha começa com f e capturamos o nome do arquivo em um hash se tivermos um descritor de arquivo válido e um nome de arquivo válido. Apenas no caso, se nenhuma dessas condições foram atendidas, nós limpamos $fd . Depois que todas as linhas são processadas, numericamente classificamos as chaves (nossos descritores de arquivo) do nosso hash, armazenamos os resultados no array x e geramos o conteúdo do hash p dos nomes dos arquivos, indexados pelo último elemento (o maior valor) na matriz x .

A única questão é: será lsof instalado em todos os sistemas e quão estável é este formato de saída.

    
por 12.05.2015 / 19:38