Maneira portátil de obter o caminho absoluto do script?

25

O que é uma maneira portátil para um script (zsh) determinar seu caminho absoluto?

No Linux eu uso algo como

mypath=$(readlink -f $0)

... mas isso não é portátil. (Por exemplo, readlink on darwin não reconhece o -f flag, nem tem nenhum equivalente.) (Além disso, usar readlink para isso é, reconhecidamente, um hack bastante obscuro).

O que é uma maneira mais portátil?

    
por kjo 20.05.2013 / 23:30

7 respostas

24

Com zsh , é apenas:

mypath=$0:A

Agora para outros shells, embora realpath() e readlink() sejam funções padrão (sendo a última uma chamada de sistema), realpath e readlink não são comandos padrão, embora alguns sistemas tenham um ou o outro ou ambos com vários comportamentos e conjuntos de recursos.

Como frequentemente, para portabilidade, você pode querer recorrer a perl :

abs_path() {
  perl -MCwd -le '
    for (@ARGV) {
      if ($p = Cwd::abs_path $_) {
        print $p;
      } else {
        warn "abs_path: $_: $!\n";
        $ret = 1;
      }
    }
    exit $ret' "$@"
}

Isso se comportaria mais como readlink -f do GNU do que realpath() (GNU readlink -e ), pois não reclamará se o arquivo não existir, desde que o seu nome de diretório seja.

    
por 11.06.2014 / 12:17
20

No zsh, você pode fazer o seguinte:

mypath=${0:a}

Ou para obter o diretório no qual o script reside:

mydir=${0:a:h}

Fonte: zshexpn (1) man page, seção HISTORY EXPANSION, subseção Modifiers (ou simplesmente info -f zsh -n Modifiers ).

    
por 16.02.2014 / 17:22
11

Estou usando isso há vários anos:

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")
    
por 21.05.2013 / 18:25
8

Esta sintaxe deve ser portável para qualquer interpretador de estilo de shell Bourne (testado com bash , ksh88 , ksh93 , zsh , mksh , dash e busybox sh ):

mypath=$(exec 2>/dev/null;cd -- $(dirname "$0"); unset PWD; /usr/bin/pwd || /bin/pwd || pwd)
echo mypath=$mypath

Esta versão adiciona compatibilidade ao shell legado do AT & T Bourne (não POSIX):

mypath='dirname "$0"'
mypath='exec 2>/dev/null;(cd -- "$mypath") && cd -- "$mypath"|| cd "$mypath"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd'
echo mypath=$mypath
    
por 21.05.2013 / 01:32
4

Supondo que você realmente quis dizer o caminho absoluto, ou seja, um caminho do diretório raiz:

case $0 in
  /*) mypath=$0;;
  *) mypath=$PWD/$0;;
esac

Isso funciona em qualquer shell estilo Bourne, a propósito.

Se você quis dizer um caminho com todos os links simbólicos resolvidos, essa é uma questão diferente. readlink -f funciona no Linux (excluindo alguns sistemas BusyBox despojados), FreeBSD, NetBSD, OpenBSD e Cygwin, mas não no OS / X, AIX, HP / UX ou Solaris. Se você tem readlink , você pode chamá-lo em um loop:

realpath () {
  [ -e "$1" ] || return
  case $1 in
    /*) :;;
    *) set "$PWD/$1";;
  esac
  while [ -L "$1" ]; do
    set "${1%/*}" "$(readlink "$1")"
    case $2 in
      /*) set "$2";;
      *) if [ -z "$1" ]; then set "/$2"; else set "$(cd "$1" && pwd -P)/$2"; fi;;
    esac
  done
  case $1 in
    */.|*/..) set "$(cd "$1" && pwd -P)";;
    */./*|*/../*) set "$(cd "${1%/*}" && pwd -P)/${1##*/}"
  esac
  realpath=$1
}

Se você não tiver readlink , poderá aproximá-lo com ls -n , mas isso só funcionará se ls não manipular nenhum caractere não imprimível no nome do arquivo.

poor_mans_readlink () {
  if [ -L "$1" ]; then
    set -- "$1" "$(LC_ALL=C command ls -n -- "$2"; echo z)"
    set -- "${2%??}"
    set -- "${2#*"$1 -> "}"
  fi
  printf '%s\n' "$1"
}

(O z extra é para o caso de o destino do link terminar em uma nova linha, que a substituição de comandos iria consumir. A propósito, a função realpath não manipula os nomes dos diretórios.)

    
por 21.05.2013 / 01:00
1

Desde que você tenha permissões de execução no diretório atual - ou no diretório a partir do qual você executou seu script de shell - se você quiser um caminho absoluto para um diretório, tudo o que você precisa é cd .

Etapa 10 da especificação de cd

If the -P option is in effect, the $PWD environment variable shall be set to the string that would be output by pwd -P. If there is insufficient permission on the new directory, or on any parent of that directory, to determine the current working directory, the value of the $PWD environment variable is unspecified.

E em pwd -P

The pathname written to standard output shall not contain any components that refer to files of type symbolic link. If there are multiple pathnames that the pwd utility could write to standard output, one beginning with a single /slash character and one or more beginning with two /slash characters, then it shall write the pathname beginning with a single /slash character. The pathname shall not contain any unnecessary /slash characters after the leading one or two /slash characters.

É porque cd -P precisa definir o diretório de trabalho atual como pwd -P caso queira imprimir e que cd - tenha que imprimir o $OLDPWD que o seguinte funciona:

mkdir ./dir
ln -s ./dir ./ln
cd ./ln ; cd . ; cd -

OUTPUT

/home/mikeserv/test/ln

espere por isso ...

cd -P . ; cd . ; cd -

OUTPUT

/home/mikeserv/test/dir

E quando imprimo com cd - , estou imprimindo $OLDPWD . cd define $PWD assim que eu cd -P . $PWD agora é um caminho absoluto para / - então não preciso de outras variáveis. E, na verdade, eu nem deveria precisar do . à direita, mas há um comportamento especificado de redefinir $PWD to $HOME em um shell interativo quando cd é sem adornos. Então, é apenas um bom hábito para se desenvolver.

Portanto, fazer o acima no caminho em ${0%/*} deve ser mais do que suficiente para verificar o caminho de $0 , mas no caso de $0 ser um link flexível, você provavelmente não poderá alterar o diretório para infelizmente.

Aqui está uma função que irá lidar com isso:

zpath() { cd -P . || return
    _out() { printf "%s$_zdlm\n" "$PWD/${1##*/}"; }
    _cd()  { cd -P "$1" ; } >/dev/null 2>&1
    while [ $# -gt 0 ] && _cd .
    do  if     _cd "$1" 
        then   _out
        elif ! [ -L "$1" ] && [ -e "$1" ] 
        then   _cd "${1%/*}"; _out "$1"
        elif   [ -L "$1" ]
        then   ( while set -- "${1%?/}"; _cd "${1%/*}"; [ -L "${1##*/}" ]
                 do    set " $1" "$(_cd -; ls -nd -- "$1"; echo /)"
                       set -- "${2#*"$1" -> }"
                 done; _out "$1" 
    );  else   ( PS4=ERR:\ NO_SUCH_PATH; set -x; : "$1" )
    fi; _cd -; shift; done
    unset -f _out _cd; unset -v _zdlm                                    
}

Ele se esforça para fazer o máximo possível no shell atual - sem invocar um subshell - embora haja subshells invocados para erros e links de software que não apontam para diretórios. Depende de um shell compatível com POSIX e de ls compatível com POSIX, além de um namespace _function() limpo. Ele ainda funcionará bem sem o último, embora possa sobrescrever então unset algumas funções atuais do shell nesse caso. Em geral, todas essas dependências devem estar bastante confiáveis em uma máquina Unix.

Chamado com ou sem argumentos, a primeira coisa que ele faz é redefinir $PWD para seu valor canônico - ele resolve todos os links para seus destinos conforme necessário. Chamado sem argumentos e é sobre isso; mas ligou com eles e vai resolver e canonizar o caminho para cada um ou então imprimir uma mensagem para stderr porque não.

Como ele opera principalmente no shell atual, ele deve ser capaz de manipular uma lista de argumentos de qualquer tamanho. Ele também procura pela variável $_zdlm (que também unset s quando ela passa) e imprime seu valor de escape C imediatamente à direita de cada um de seus argumentos, cada um dos quais é sempre seguido também por um único caractere de ewline \n .

Ele faz um monte de mudanças no diretório, mas, além de configurá-lo para o seu valor canônico, ele não afeta $PWD , embora $OLDPWD não possa ser de forma alguma contado quando ele for finalizado.

Ele tenta encerrar cada um dos seus argumentos assim que possível. Primeiro tenta cd em $1 . Se ele pode imprimir o caminho canônico do argumento para stdout . Se não puder, verificará que $1 existe e não é um link flexível. Se for verdade, imprime.

Dessa forma, ele manipula qualquer argumento de tipo de arquivo ao qual o shell tenha permissões para endereçar, a menos que $1 seja um link simbólico que não aponte para um diretório. Nesse caso, ele chama while loop em um subshell.

Chama ls para ler o link. O diretório atual deve ser alterado para o seu valor inicial primeiro, a fim de lidar de forma confiável com qualquer caminho de referência e, assim, na sub-rotina de substituição de comando que a função faz:

cd -...ls...echo /

Ele retira da esquerda da saída de ls o mínimo necessário para conter completamente o nome do link e a string -> . Embora eu tenha tentado evitar fazer isso com shift e $IFS , esse é o método mais confiável e o mais próximo possível. É a mesma coisa que o poor_mans_readlink do Gilles faz - e está bem feito.

Ele repetirá esse processo em um loop até que o nome do arquivo retornado de ls definitivamente não seja um link flexível. Nesse ponto, canoniza esse caminho como antes com cd , em seguida, imprime.

Exemplo de uso:

zpath \
    /tmp/script \   #symlink to $HOME/test/dir/script.sh
    ln \            #symlink to ./dir/
    ln/nl \         #symlink to ../..
    /dev/fd/0 \     #currently a here-document like : dash <<\HD
    /dev/fd/1 \     #(zlink) | dash
    file \          #regular file                                             
    doesntexist \   #doesnt exist
    /dev/disk/by-path/pci-0000:00:16.2-usb-0:3:1.0-scsi-0:0:0:0 \
    /dev/./././././././null \
    . ..      

OUTPUT

/home/mikeserv/test/dir/script.sh
/home/mikeserv/test/dir/
/home/mikeserv/test/
/tmp/zshtpKRVx (deleted)
/proc/17420/fd/pipe:[1782312]
/home/mikeserv/test/file
ERR: NO_SUCH_PATH: doesntexist
/dev/sdd
/dev/null
/home/mikeserv/test/
/home/mikeserv/

Ou possivelmente ...

ls
dir/  file  file?  folder/  link@  ln@  script*  script3@  script4@

zdlm=\0 zpath * | cat -A

OUTPUT

/home/mikeserv/test/dir/^@$               
/home/mikeserv/test/file^@$
/home/mikeserv/test/file$
^@$
/home/mikeserv/test/folder/^@$
/home/mikeserv/test/file$               #'link' -> 'file\n'
^@$
/home/mikeserv/test/dir/^@$             #'ln' -> './dir'
/home/mikeserv/test/script^@$
/home/mikeserv/test/dir/script.sh^@$    #'script3' -> './dir/script.sh'
/home/mikeserv/test/dir/script.sh^@$    #'script4' -> '/tmp/script' -> ...
    
por 27.08.2014 / 20:40
0

que tal uma linha de apoio simpática quando há python disponível para impedir a redefinição de um algoritmo?

function readlink { python -c "import os.path; print os.path.realpath('$1')"; }

o mesmo que o link

    
por 29.07.2017 / 03:01