Como posso obter o comportamento de -f readlink do GNU em um Mac?
Pergunta
No Linux, o utilitário readlink
aceita um -f
opção que segue links adicionais. Isso não parece trabalhar em Mac e sistemas possivelmente baseados BSD. Qual seria o ser equivalente?
Aqui estão algumas informações de depuração:
$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
Solução
readlink -f
faz duas coisas:
- Ele itera ao longo de uma sequência de links simbólicos até encontrar um arquivo real.
- Ele retorna desse arquivo canonicalized nome-i., O seu caminho absoluto.
Se você quiser, você pode simplesmente construir um shell script que usa baunilha comportamento readlink para conseguir a mesma coisa. Aqui está um exemplo. Obviamente, você pode inserir isso em seu próprio script onde você gostaria de chamar readlink -f
#!/bin/sh
TARGET_FILE=$1
cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`
# Iterate down a (possible) chain of symlinks
while [ -L "$TARGET_FILE" ]
do
TARGET_FILE=`readlink $TARGET_FILE`
cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`
done
# Compute the canonicalized name by finding the physical path
# for the directory we're in and appending the target file.
PHYS_DIR=`pwd -P`
RESULT=$PHYS_DIR/$TARGET_FILE
echo $RESULT
Note não que isso não inclui qualquer tratamento de erros. De particular importância, ele não detectar ciclos link simbólico. Uma maneira simples de fazer isso seria para contar o número de vezes que você vá em torno do laço e falhar se você acertar um número improvável grande, como 1000.
editado para usar pwd -P
vez de $PWD
.
Note que esta espera de script para ser chamado como ./script_name filename
, não -f
, mudança $1
para $2
se você quer ser capaz de usar com -f filename
como GNU readlink.
Outras dicas
MacPorts e Homebrew fornecer um coreutils pacote contendo greadlink
(GNU readlink). Crédito para Michael Kallweitt pós em mackb.com.
brew install coreutils
greadlink -f file.txt
Você pode estar interessado em realpath(3)
ou Python do os.path.realpath
. Os dois não são exatamente o mesmo; a chamada biblioteca C requer que existem componentes de caminho intermediário, enquanto a versão Python não.
$ pwd
/tmp/foo
$ ls -l
total 16
-rw-r--r-- 1 miles wheel 0 Jul 11 21:08 a
lrwxr-xr-x 1 miles wheel 1 Jul 11 20:49 b -> a
lrwxr-xr-x 1 miles wheel 1 Jul 11 20:49 c -> b
$ python -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' c
/private/tmp/foo/a
Eu sei que você disse que você iria preferir algo mais leve do que outra linguagem de script, mas apenas no caso de compilar um binário é insuportável, você pode usar Python e ctypes (disponível no Mac OS X 10.5) para embrulhar a chamada biblioteca:
#!/usr/bin/python
import ctypes, sys
libc = ctypes.CDLL('libc.dylib')
libc.realpath.restype = ctypes.c_char_p
libc.__error.restype = ctypes.POINTER(ctypes.c_int)
libc.strerror.restype = ctypes.c_char_p
def realpath(path):
buffer = ctypes.create_string_buffer(1024) # PATH_MAX
if libc.realpath(path, buffer):
return buffer.value
else:
errno = libc.__error().contents.value
raise OSError(errno, "%s: %s" % (libc.strerror(errno), buffer.value))
if __name__ == '__main__':
print realpath(sys.argv[1])
Ironicamente, a versão C deste script deveria ser mais curto. :)
Eu odeio a pilha com uma outra aplicação, mas eu precisava a) a, implementação shell puro portátil , e b) a cobertura unidade de teste , como o número de ponta-casos para algo como isso são não-trivial .
meu projeto no Github para testes e código completo. O que se segue é uma sinopse da implementação:
Como Keith Smith aponta astutamente, readlink -f
faz duas coisas: 1) resolve links simbólicos de forma recursiva, e 2) Canonicaliza o resultado, portanto:
realpath() {
canonicalize_path "$(resolve_symlinks "$1")"
}
Primeiro, a implementação resolver ligação simbólica:
resolve_symlinks() {
local dir_context path
path=$(readlink -- "$1")
if [ $? -eq 0 ]; then
dir_context=$(dirname -- "$1")
resolve_symlinks "$(_prepend_path_if_relative "$dir_context" "$path")"
else
printf '%s\n' "$1"
fi
}
_prepend_path_if_relative() {
case "$2" in
/* ) printf '%s\n' "$2" ;;
* ) printf '%s\n' "$1/$2" ;;
esac
}
Note que esta é uma versão ligeiramente simplificada do a plena implementação . A aplicação integral adiciona uma pequena seleção para ciclos link simbólico , bem como massagens a saída um pouco.
Finalmente, a função para Canonicalização um caminho:
canonicalize_path() {
if [ -d "$1" ]; then
_canonicalize_dir_path "$1"
else
_canonicalize_file_path "$1"
fi
}
_canonicalize_dir_path() {
(cd "$1" 2>/dev/null && pwd -P)
}
_canonicalize_file_path() {
local dir file
dir=$(dirname -- "$1")
file=$(basename -- "$1")
(cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
}
É isso, mais ou menos. bastante simples para colar em seu script, mas o suficiente complicado que você seria louco de confiar em qualquer código que não tem testes de unidade para seus casos de uso.
- Instalar homebrew
- Executar "Brew instalar coreutils"
- Executar "caminho -f greadlink"
greadlink é a readlink gnu que implementos -f. Você pode usar MacPorts ou outros, bem, eu prefiro homebrew.
Um simples one-liner em perl que é certo para o trabalho quase todos os lugares, sem quaisquer dependências externas:
perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file
Will links simbólicos dereference.
Uso em um script poderia ser assim:
readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"
Eu fiz um script chamado realpath pessoalmente que se parece um pouco algo como:
#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])
O que sobre isso?
function readlink() {
DIR="${1%/*}"
(cd "$DIR" && echo "$(pwd -P)")
}
FreeBSD e OSX tiver uma versão do stat
derived do NetBSD.
Você pode ajustar a saída com interruptores de formato (veja as páginas de manual nos links acima).
% cd /service
% ls -tal
drwxr-xr-x 22 root wheel 27 Aug 25 10:41 ..
drwx------ 3 root wheel 8 Jun 30 13:59 .s6-svscan
drwxr-xr-x 3 root wheel 5 Jun 30 13:34 .
lrwxr-xr-x 1 root wheel 30 Dec 13 2013 clockspeed-adjust -> /var/service/clockspeed-adjust
lrwxr-xr-x 1 root wheel 29 Dec 13 2013 clockspeed-speed -> /var/service/clockspeed-speed
% stat -f%R clockspeed-adjust
/var/service/clockspeed-adjust
% stat -f%Y clockspeed-adjust
/var/service/clockspeed-adjust
Algumas versões OS X de stat
podem não ter a opção -f%R
para formatos. Neste caso -stat -f%Y
pode ser suficiente. A opção -f%Y
mostrará o alvo de um link simbólico, enquanto mostra -f%R
o caminho absoluto correspondente ao arquivo.
EDIT:
Se você é capaz de usar Perl (Darwin / OS X vem instalado com recentes verions de perl
), então:
perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt
vai funcionar.
Aqui está uma função shell portátil que devem trabalhar em ANY Bourne shell comparável. Ele irá resolver a pontuação caminho relativo ".. ou." e interpreta ligações simbólicas.
Se por algum motivo você não tem um realpath (1) de comando, ou readlink (1) este pode ser alias.
which realpath || alias realpath='real_path'
Enjoy:
real_path () {
OIFS=$IFS
IFS='/'
for I in $1
do
# Resolve relative path punctuation.
if [ "$I" = "." ] || [ -z "$I" ]
then continue
elif [ "$I" = ".." ]
then FOO="${FOO%%/${FOO##*/}}"
continue
else FOO="${FOO}/${I}"
fi
## Resolve symbolic links
if [ -h "$FOO" ]
then
IFS=$OIFS
set `ls -l "$FOO"`
while shift ;
do
if [ "$1" = "->" ]
then FOO=$2
shift $#
break
fi
done
IFS='/'
fi
done
IFS=$OIFS
echo "$FOO"
}
Além disso, apenas no caso de alguém estiver interessado aqui é como implementar basename e dirname em código shell 100% puro:
## http://www.opengroup.org/onlinepubs/000095399/functions/dirname.html
# the dir name excludes the least portion behind the last slash.
dir_name () {
echo "${1%/*}"
}
## http://www.opengroup.org/onlinepubs/000095399/functions/basename.html
# the base name excludes the greatest portion in front of the last slash.
base_name () {
echo "${1##*/}"
}
Você pode encontrar a versão atualizada deste código shell no meu site google: http: // sites. google.com/site/jdisnard/realpath
EDIT: Este código é licenciada sob os termos da licença 2-clause (estilo FreeBSD). Uma cópia da licença pode ser encontrada seguindo o link acima para o meu site.
A maneira mais fácil de resolver este problema e permitir a funcionalidade do readlink no Mac w / Homebrew instalado ou FreeBSD é instalar pacote 'coreutils'. Também pode ser necessária em determinadas distribuições Linux e outros POSIX OS.
Por exemplo, em FreeBSD 11, eu instalei invocando:
# pkg install coreutils
Em MacOS com Homebrew, o comando seria:
$ brew install coreutils
Na verdade, não sei por que as outras respostas são tão complicados, isso é tudo que existe para ela. Os arquivos não estão em um lugar diferente, eles estão apenas ainda não instalado.
Comece a atualização
Este é um problema tão frequente que temos juntos uma biblioteca Bash 4 para uso gratuito (MIT License) chamado realpath-lib . Isto é projetado para emular readlink -f por padrão e inclui dois conjuntos de testes para verificar (1) que trabalha para um determinado sistema Unix e (2) contra readlink -f se instalado (mas isso não é obrigatório). Além disso, ele pode ser usado para investigar, identificar e profunda descontrair, links simbólicos quebrados e referências circulares, por isso pode ser uma ferramenta útil para o diagnóstico de problemas de arquivos e diretórios físicos ou simbólicos profundamente aninhadas. Ela pode ser encontrada em github.com ou bitbucket.org .
Fim Atualização
Outra solução muito compacta e eficiente, que não depende de nada, mas Bash é:
function get_realpath() {
[[ ! -f "$1" ]] && return 1 # failure : file does not exist.
[[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
return 0 # success
}
Isto também inclui um ambiente definindo no_symlinks
que fornece a capacidade de links simbólicos resolver para o sistema físico. Enquanto no_symlinks
está definido para algo, ou seja, no_symlinks='on'
seguida, links simbólicos serão resolvidos para o sistema físico. Caso contrário, eles serão aplicados (a configuração padrão).
Isso deve funcionar em qualquer sistema que fornece Bash, e irá retornar um código de saída compatível Bash para fins de teste.
Já há um monte de respostas, mas nenhuma funcionou para mim ... Então é isso que eu estou usando agora.
readlink_f() {
local target="$1"
[ -f "$target" ] || return 1 #no nofile
while [ -L "$target" ]; do
target="$(readlink "$target")"
done
echo "$(cd "$(dirname "$target")"; pwd -P)/$target"
}
Melhor tarde do que nunca, eu suponho. I foi motivada para desenvolver este especificamente porque meus scripts do Fedora não estavam trabalhando no Mac. O problema é dependências e Bash. Macs não tê-los, ou se o fizerem, eles são muitas vezes em outro lugar (outro caminho). Dependência manipulação caminho em um cross-platform Bash script é uma dor de cabeça no melhor e um risco de segurança na pior das hipóteses -. Por isso é melhor evitar o seu uso, se possível
A função get_realpath () abaixo é simples, Bash centrado, e não há dependências são necessárias. I utiliza apenas os builtins Bash echo e cd . Ele também é bastante segura, como tudo é testado em cada etapa do caminho e ele retorna condições de erro.
Se você não quiser seguir links simbólicos, em seguida, colocar set -P na frente do script, mas por outro cd deve resolver os links simbólicos por padrão. Tem sido testado com argumentos de arquivo que são {absoluta | relativa | symlink | locais} e retorna o caminho absoluto para o arquivo. Até agora não tive qualquer problema com ele.
function get_realpath() {
if [[ -f "$1" ]]
then
# file *must* exist
if cd "$(echo "${1%/*}")" &>/dev/null
then
# file *may* not be local
# exception is ./file.ext
# try 'cd .; cd -;' *works!*
local tmppwd="$PWD"
cd - &>/dev/null
else
# file *must* be local
local tmppwd="$PWD"
fi
else
# file *cannot* exist
return 1 # failure
fi
# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success
}
Você pode combinar isso com outras funções get_dirname, get_filename, get_stemname e validate_path. Estes podem ser encontrados em nosso repositório GitHub como realpath-lib (divulgação completa - este é o nosso produto mas oferecê-lo gratuitamente para a comunidade, sem quaisquer restrições). Ele também pode servir como uma ferramenta de ensino. - É bem documentado
Nós tentamos o nosso melhor para aplicar práticas ditas 'modernas Bash', mas Bash é um grande assunto e estou certo de que sempre haverá espaço para melhorias. Ela exige Bash 4+ mas poderia ser feito para trabalhar com versões mais antigas se eles ainda estão por aí.
Verdadeiramente plataforma indpendent seria também este R-onliner
readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";}
Para readlink -f <path>
realmente imitar, $ 2 em vez de US $ 1 terá de ser utilizado.
Uma vez que o meu trabalho é usado por pessoas com não-BSD Linux, bem como MacOS, eu optou por usar esses aliases em nossos scripts de construção (sed
incluída uma vez que tem problemas semelhantes):
##
# If you're running macOS, use homebrew to install greadlink/gsed first:
# brew install coreutils
#
# Example use:
# # Gets the directory of the currently running script
# dotfilesDir=$(dirname "$(globalReadlink -fm "$0")")
# alias al='pico ${dotfilesDir}/aliases.local'
##
function globalReadlink () {
# Use greadlink if on macOS; otherwise use normal readlink
if [[ $OSTYPE == darwin* ]]; then
greadlink "$@"
else
readlink "$@"
fi
}
function globalSed () {
# Use gsed if on macOS; otherwise use normal sed
if [[ $OSTYPE == darwin* ]]; then
gsed "$@"
else
sed "$@"
fi
}
verificação opcional você pode adicionar para instalar automaticamente homebrew + coreutils dependências:
if [[ "$OSTYPE" == "darwin"* ]]; then
# Install brew if needed
if [ -z "$(which brew)" ]; then
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)";
fi
# Check for coreutils
if [ -z "$(brew ls coreutils)" ]; then
brew install coreutils
fi
fi
Eu suponho que para ser verdadeiramente "global" que precisa de verificar outros ... mas que provavelmente se aproxima da marca de 80/20.
Eu escrevi um utilitário href="https://github.com/user454322/realpath" realpath para OS X que pode fornecer os mesmos resultados que readlink -f
.
Aqui está um exemplo:
(jalcazar@mac tmp)$ ls -l a
lrwxrwxrwx 1 jalcazar jalcazar 11 8月 25 19:29 a -> /etc/passwd
(jalcazar@mac tmp)$ realpath a
/etc/passwd
Se você estiver usando MacPorts, você pode instalá-lo com o seguinte comando:. sudo port selfupdate && sudo port install realpath
Explicação
coreutils é um pacote brew
que instala utilitários núcleo GNU / Linux que correspondem à implementação Mac OSX deles para que você possa usá-los
Você pode encontrar programas ou utilties em seu sistema Mac OSX que parecem semelhantes aos coreutils Linux ( "Utilitários Básicos") mas diferem em alguns aspectos (como ter diferentes bandeiras).
Este é porque a implementação Mac OSX dessas ferramentas são diferentes. Para obter o GNU originais / Linux-like comportamento que você pode instalar o pacote coreutils
através do sistema de gerenciamento de pacotes brew
.
Isto irá instalar os utilitários principais, o prefixo g
correspondente. Por exemplo. para readlink
, você vai encontrar um programa greadlink
correspondente.
A fim de fazer readlink
executar como a implementação GNU readlink
(greadlink
), você pode fazer um alias de simples depois de instalar coreutils.
Implementação
- Instale bebida
Siga as instruções em https://brew.sh/
- Instale o pacote coreutils
brew install coreutils
- Criar um Alias ??
Você pode colocar seu alias em ~ / .bashrc, ~ / .bash_profile, ou onde quer que você está acostumado a manter seus aliases bash. Eu, pessoalmente, manter a mina em ~ / .bashrc
alias readlink=greadlink
Você pode criar aliases semelhantes para outros coreutils como gmv, gdu, gdf, e assim por diante. Mas cuidado que o comportamento GNU em uma máquina mac pode ser confuso para os outros acostumados a trabalhar com coreutils nativas, ou podem se comportar de forma inesperada em seu sistema Mac.
Isto é o que eu uso:
stat -f %N $your_path
Os caminhos para readlink são diferentes entre meu sistema e seu. Por favor, tente especificando o caminho completo:
/sw/sbin/readlink -f
Perl tem uma função readlink (por exemplo Como faço para copiar links simbólicos em Perl? ). Isso funciona na maioria das plataformas, incluindo OS X:
perl -e "print readlink '/path/to/link'"
Por exemplo:
$ mkdir -p a/b/c
$ ln -s a/b/c x
$ perl -e "print readlink 'x'"
a/b/c
A resposta de @Keith Smith dá um loop infinito.
Aqui está a minha resposta, que eu uso apenas em SunOS (SunOS perca tanto comandos POSIX e GNU).
É um arquivo de script que você tem que colocar em um de seus diretórios $ PATH:
#!/bin/sh
! (($#)) && echo -e "ERROR: readlink <link to analyze>" 1>&2 && exit 99
link="$1"
while [ -L "$link" ]; do
lastLink="$link"
link=$(/bin/ls -ldq "$link")
link="${link##* -> }"
link=$(realpath "$link")
[ "$link" == "$lastlink" ] && echo -e "ERROR: link loop detected on $link" 1>&2 && break
done
echo "$link"