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' -> ...