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

29

O que é uma maneira portátil de 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, readlinkno darwin não reconhece a -fbandeira, nem tem equivalente.) (Além disso, usar readlinkpara isso é, na verdade, um truque bastante obscuro.)

O que é uma maneira mais portátil?

kjo
fonte
1
Veja também $ 0 sempre incluirá o caminho para o script?
Stéphane Chazelas

Respostas:

28

Com zsh, é apenas:

mypath=$0:A

Agora para outras conchas, embora realpath()e readlink()são funções padrão (o último sendo uma chamada de sistema), realpathe readlinknão são de comando padrão, embora alguns sistemas têm um ou outro ou ambos com vários comportamentos e conjunto de recursos.

Com frequência, para portabilidade, você pode 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 o GNU do readlink -fque realpath()(GNU readlink -e), pois ele não reclamará se o arquivo não existir enquanto o nome do diretório existir.

Stéphane Chazelas
fonte
Nota: isso não funciona para o seu .zshrc: consulte esta postagem .
Bryce Guinta
24

No zsh, você pode fazer o seguinte:

mypath=${0:a}

Ou, para obter o diretório em que o script reside:

mydir=${0:a:h}

Fonte: página de manual zshexpn (1), seção EXPANSÃO DE HISTÓRIA, Modificadores de subseção (ou simplesmente info -f zsh -n Modifiers).

Mrmenken
fonte
Doce! Eu tenho procurado por algo assim há muito tempo e leio todo o conjunto de páginas do zsh procurando, mas nunca me ocorreu procurar em 'Expansão da história'.
Vucar Timnärakrul
1
O equivalente a GNU's readlink -fpreferiria ser $0:A.
Stéphane Chazelas
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")")
user17591
fonte
1
eu gosto disso! até agora, isso é bastante portátil. funciona em Solaris, OmniOS, Linux, Mac, e até mesmo Cygwin no Windows 2008.
Tim Kennedy
4
Onde não é equivalente ao GNU readlink -fé quando o próprio script é um link simbólico.
Stéphane Chazelas
No Ubuntu 16.04 com zsh, se eu executar isso diretamente ou no subshell (como sugerido) enquanto estiver no meu diretório home ( /home/ville), ele será impresso /home/ville/zsh.
Ville
8

Esta sintaxe deve ser portável para qualquer intérprete estilo shell Bourne (testado com bash, ksh88, ksh93, zsh, mksh, dashe 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 AT&T Bourne herdado (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
jlliagre
fonte
obrigado. no entanto, acho que a desconexão $PWDpode ser um exagero - você pode configurá-la como uma corrente absoluta cd -P .. Duvido que funcione em bourneshell - mas deve funcionar em todos os que você testou pela primeira vez. faz por mim de qualquer maneira.
mikeserv
@moose Qual sistema operacional você está executando?
Jlliagre
quem é alce? o que?
precisa saber é o seguinte
@mikeserv alce é um transeunte que postou um comentário sobre algum problema com zshe dirnamemas rapidamente retirar hir / seu comentário ...
jlliagre
Seu script Bourne Shell não funcionará com um Bourne Shell, pois o Bourne Shell não usa getopt () para cd (1).
schily
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

A propósito, isso funciona em qualquer shell no estilo Bourne.

Se você quis dizer um caminho com todos os links simbólicos resolvidos, isso é outra questão. readlink -ffunciona no Linux (excluindo alguns sistemas BusyBox simplificados), FreeBSD, NetBSD, OpenBSD e Cygwin, mas não no OS / X, AIX, HP / UX ou Solaris. Se você tiver 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 possui readlink, é possível aproximar ls -n, mas isso só funciona se lsnão alterar 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 extra zé no caso de o destino do link terminar em uma nova linha, cuja substituição de comando consumiria de outra forma. A realpathfunção não trata desse caso para nomes de diretório, a propósito.)

Gilles 'SO- parar de ser mau'
fonte
1
Você conhece alguma lsimplementação que manipule caracteres não imprimíveis quando a saída não for para um terminal.
Stéphane Chazelas
1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(ou seja, se o nome estiver em UTF-8, latin1 oferece um único ?). Acho que já vi isso também em escritórios comerciais mais antigos.
Gilles 'SO- stop be evil'
@ StéphaneChazelas Corrigi vários bugs, mas não testei extensivamente. Deixe-me saber se ele ainda falhar em alguns casos (além da falta de permissões de execução em alguns diretórios, não vou lidar com esse caso extremo).
Gilles 'SO- stop be evil'
@Gilles - o que busyboxé isso? de acordo com o git busybox ls não mudou de código desde 2011. Meu busybox ls- por volta de 2013 - não faz isso. Este um - circa 2012 - faz . Isso pode explicar o porquê. Você construiu seu busyboxcom suporte a Unicode - para incluir suporte a wchar? Você pode tentar, ou mais, para verificar as opções de compilação no mkinitcpio busyboxpacote.
mikeserv
Gilles - acredito que inicialmente tenha julgado mal essa resposta - ou pelo menos uma parte dela. Embora eu acredite firmemente que o mantra de seus nomes de arquivos é uma falácia absoluta, definitivamente seu poor_mans_readlink está muito bem feito. Se você me fizer a gentileza de fazer uma edição - qualquer edição fará - e me enviar um ping depois, eu gostaria de reverter meu voto.
mikeserv
1

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

Etapa 10 das cdespecificações de

Se a -Popção estiver em vigor, a $PWDvariável de ambiente deve ser configurada para a string que seria emitida por pwd -P. Se houver permissão insuficiente no novo diretório, ou em qualquer pai desse diretório, para determinar o diretório de trabalho atual, o valor da $PWDvariável de ambiente não será especificado.

E em pwd -P

O nome do caminho gravado na saída padrão não deve conter nenhum componente que se refira aos arquivos do tipo link simbólico. Se houver vários nomes de caminho que o pwdutilitário possa gravar na saída padrão, um começando com um caractere único / barra e um ou mais começando com dois caracteres / barra, ele deverá escrever o nome do caminho começando com um caractere único / barra. O nome do caminho não deve conter caracteres desnecessários / barra após os caracteres um ou dois iniciais / barra.

É porque cd -Pé necessário definir o diretório de trabalho atual como o que pwd -Pdeve ser impresso e que cd -deve ser impresso $OLDPWDconforme o seguinte:

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

SAÍDA

/home/mikeserv/test/ln

espere por isso...

cd -P . ; cd . ; cd -

SAÍDA

/home/mikeserv/test/dir

E quando eu imprimo com cd -estou imprimindo $OLDPWD. cddefine $PWDassim que cd -P . $PWDagora for um caminho absoluto para /- então não preciso de outras variáveis. E, na verdade, eu nem deveria precisar da trilha, .mas há um comportamento especificado de redefinir $PWDpara $HOMEem um shell interativo quando não cdé adornado. Portanto, é apenas um bom hábito para se desenvolver.

Portanto, basta fazer o acima no caminho ${0%/*}deve ser mais do que suficiente para verificar $0o caminho, mas, no caso em $0si, é um link direto, você provavelmente não poderá alterar o diretório nele, 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 que pode no shell atual - sem chamar um subshell - embora existam subshells invocados para erros e links flexíveis que não apontam para diretórios. Depende de um shell compatível com POSIX e de um espaço para nome lslimpo, compatível com POSIX _function(). Ele ainda funcionará bem sem o último, embora possa sobrescrever unsetalgumas funções atuais do shell nesse caso. Em geral, todas essas dependências devem estar disponíveis de maneira confiável em uma máquina Unix.

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

Como ele opera principalmente no shell atual, deve ser capaz de lidar com uma lista de argumentos de qualquer tamanho. Ele também procura a $_zdlmvariável (que também unseté quando\n termina ) e imprime seu valor escapado de C imediatamente à direita de cada um de seus argumentos, sendo que cada um deles é sempre seguido também por um único caractere de linha de linha.

Ele altera bastante o diretório, mas, além de defini-lo com seu valor canônico, ele não afeta $PWD, embora $OLDPWDnão possa, de forma alguma, ser contado quando terminar.

Ele tenta encerrar cada um de seus argumentos o quanto antes. Ele primeiro tenta cdentrar $1. Se puder, imprime o caminho canônico do argumento para stdout. Se não puder, verifica se $1existe e não é um link flexível. Se verdadeiro, ele imprime.

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

Chama lspara ler o link. O diretório atual deve ser alterado para seu valor inicial primeiro, a fim de manipular com segurança quaisquer caminhos de referência e, portanto, no subshell de substituição de comando, a função:

cd -...ls...echo /

Ele tira da esquerda da lssaída o mínimo necessário para conter completamente o nome do link e a string ->. Embora eu tenha tentado evitar isso shifte, ao que $IFSparece, esse é o método mais confiável o mais próximo possível. É a mesma coisa que o poor_mans_readlink de Gilles faz - e é bem feito.

Ele repetirá esse processo em um loop até que o nome do arquivo retornado lsdefinitivamente não seja um link vinculado. Nesse ponto, canoniza esse caminho como antes e cddepois 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 \
    . ..      

SAÍDA

/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

SAÍDA

/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' -> ...
mikeserv
fonte
0

que tal um liner simpático quando existe python disponível para impedir a redefinição de um algoritmo?

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

mesmo que https://stackoverflow.com/a/7305217

Antonio Daniel Rodriguez
fonte