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 ...]
Foi útil?

Solução

readlink -f faz duas coisas:

  1. Ele itera ao longo de uma sequência de links simbólicos até encontrar um arquivo real.
  2. 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.

  1. Instalar homebrew
  2. Executar "Brew instalar coreutils"
  3. 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 statderived 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

  1. Instale bebida

Siga as instruções em https://brew.sh/

  1. Instale o pacote coreutils

brew install coreutils

  1. 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"
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top