Como resolver links simbólicos em um script de shell


220

Dado um caminho absoluto ou relativo (em um sistema semelhante ao Unix), eu gostaria de determinar o caminho completo do destino depois de resolver quaisquer links simbólicos intermediários. Pontos de bônus por resolver também a notação de ~ nome de usuário ao mesmo tempo.

Se o destino for um diretório, pode ser possível chdir () no diretório e chamar getcwd (), mas eu realmente quero fazer isso a partir de um script de shell, em vez de escrever um auxiliar C. Infelizmente, os shells tendem a tentar ocultar a existência de links simbólicos do usuário (isso ocorre no OS X):

$ ls -ld foo bar
drwxr-xr-x   2 greg  greg  68 Aug 11 22:36 bar
lrwxr-xr-x   1 greg  greg   3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$

O que eu quero é uma função resolve () tal que, quando executada no diretório tmp no exemplo acima, resolva ("foo") == "/ Users / greg / tmp / bar".

Respostas:


92

De acordo com os padrões, pwd -Pdeve retornar o caminho com os links simbólicos resolvidos.

A função C char *getcwd(char *buf, size_t size)de unistd.hdeve ter o mesmo comportamento.

getcwd pwd


22
Isso apenas funciona para o diretório (atual)? Se o destino for um arquivo, ele não fornecerá nada ...
dala 28/04

4
Não funciona se a ligação é interrompida porque você não pode torná-lo seu caminho atual
Tom Howard

6
Para resumir com o benefício da retrospectiva: Essa resposta funciona apenas em circunstâncias muito limitadas, a saber, se o link simbólico de interesse é para um diretório que realmente existe ; Além disso, você deve cdfazê-lo primeiro, antes de ligar pwd -P. Em outras palavras: não permitirá que você resolva (consulte o destino de) links simbólicos para arquivos ou links simbólicos quebrados , e para resolver links simbólicos existentes no diretório, você precisa fazer um trabalho adicional (restaurar o diretório de trabalho anterior ou localizar as chamadas cdand pwd -Pem um subshell).
mklement0

Para ruim, procuro uma maneira de resolver um arquivo e não um diretório.
Martin

Como outros já apontaram, isso realmente não responde à pergunta. A resposta de @ pixelbeat abaixo faz.
Erstaples

401
readlink -f "$path"

Nota do editor: O texto acima funciona com o GNU readlink e FreeBSD / PC-BSD / OpenBSD readlink , mas não no OS X a partir do 10.11.
O GNU readlink oferece opções adicionais relacionadas, como -ma resolução de um link simbólico, quer o destino final exista ou não.

Observe que, desde o GNU coreutils 8.15 (06-01-2012), existe um programa de caminho real disponível que é menos obtuso e mais flexível que o anterior. Também é compatível com o utilitário FreeBSD de mesmo nome. Também inclui funcionalidade para gerar um caminho relativo entre dois arquivos.

realpath $path

[Adição de administrador abaixo do comentário por halloleo - danorton]

Para Mac OS X (até pelo menos 10.11.x), use readlinksem a -fopção:

readlink $path

Nota do editor: isso não resolverá os links simbólicos recursivamente e, portanto, não informará o destino final ; por exemplo, dado o link simbólico aque aponta para o bqual, por sua vez c, o relatório será reportado apenas b(e não garantirá que seja exibido como um caminho absoluto ).
Use o seguinteperl comando no OS X para preencher a lacuna da readlink -ffuncionalidade ausente :
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"


5
Isso não funciona no Mac OS X - consulte stackoverflow.com/questions/1055671/…
Bkkbrad

1
vergonha sobre incompatibilidade OS X, caso contrário, bom +1
jkp 29/11

11
No OS X, você pode instalar o coreutils com homebrew. Ele o instala como "grealpath".
K19:

10
readlinkfunciona no OSX, mas precisa de outra sintaxe: readlink $path sem o -f.
halloleo 28/01

2
readlink falha para de-referência várias camadas de ligação simbólica, porém, apenas derefs uma camada de cada vez
Magnus

26

"pwd -P" parece funcionar se você deseja apenas o diretório, mas se, por algum motivo, deseja o nome do executável real, acho que não ajuda. Aqui está a minha solução:

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")

# resolve symlinks
while [[ -h $SELF_PATH ]]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) get the pwd
    # 4) append the basename
    DIR=$(dirname -- "$SELF_PATH")
    SYM=$(readlink "$SELF_PATH")
    SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done

17

Um dos meus favoritos é realpath foo

realpath - retorna o nome do caminho absoluto canonizado

realpath expande todos os links simbólicos e resolve referências aos caracteres '/./', '/../' e extra '/' na cadeia terminada nula denominada path e
       armazena o nome do caminho absoluto canônico no buffer de tamanho PATH_MAX nomeado por resolved_path. O caminho resultante não terá nenhum link simbólico, '/./' ou
       componentes '/../'.

No Debian (etch e posterior), este comando está disponível no pacote realpath.
Phil Ross

2
realpath é agora (Jan 2012) parte de coreutils e compatível com o debian e BSD variante
pixelbeat

1
Eu não tenho realpathno Centos 6 com o GNU coreutils 8.4.31. Eu me deparei com vários outros no Unix e Linux que possuem um núcleo GNU empacotado sem realpath . Portanto, parece depender mais do que apenas a versão.
toxalot

Eu prefiro realpathmais readlinkporque oferece sinalizadores de opção, tais como--relative-to
arr_sea

10
readlink -e [filepath]

parece ser exatamente o que você está solicitando - ele aceita um caminho arbitrário, resolve todos os links simbólicos e retorna o caminho "real" - e é "standard * nix" que provavelmente todos os sistemas já possuem


Só fui mordido por isso. Não funciona no Mac e estou procurando por uma substituição.
dhill

5

Outra maneira:

# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 

Eu preciso de cd no myreadlink (), porque é uma função recursiva, indo para cada diretório até encontrar um link. Se ele encontrar um link, retornará o caminho real e o sed substituirá o caminho.
Keymon 19/10/19

5

Juntando algumas das soluções fornecidas, sabendo que o readlink está disponível na maioria dos sistemas, mas precisa de argumentos diferentes, isso funciona bem para mim no OSX e no Debian. Não tenho certeza sobre os sistemas BSD. Talvez a condição precise ser [[ $OSTYPE != darwin* ]]excluir -fapenas do OSX.

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"

3

Veja como é possível obter o caminho real para o arquivo no MacOS / Unix usando um script Perl embutido:

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")

Da mesma forma, para obter o diretório de um arquivo com link simbólico:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")

3

O seu caminho é um diretório ou pode ser um arquivo? Se é um diretório, é simples:

(cd "$DIR"; pwd -P)

No entanto, se for um arquivo, isso não funcionará:

DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"

porque o link simbólico pode se transformar em um caminho relativo ou completo.

Nos scripts, preciso encontrar o caminho real, para que eu possa referenciar a configuração ou outros scripts instalados junto com ele, uso o seguinte:

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done

Você pode definir SOURCEqualquer caminho do arquivo. Basicamente, enquanto o caminho for um link simbólico, ele será resolvido. O truque está na última linha do loop. Se o link simbólico resolvido for absoluto, ele será usado como SOURCE. No entanto, se for relativo, ele incluirá o prefixo DIR, que foi resolvido em um local real pelo simples truque que descrevi pela primeira vez.


2
function realpath {
    local r=$1; local t=$(readlink $r)
    while [ $t ]; do
        r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
        t=$(readlink $r)
    done
    echo $r
}

#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..

Isso quebrará com (a) qualquer caminho que contenha metacaracteres de espaço em branco ou shell e (b) links simbólicos quebrados (não é um problema se você deseja apenas o caminho pai do script em execução, assumindo que (a) não se aplica).
mklement0

Se você estiver preocupado com espaço em branco, use aspas.
Dave

Por favor, faça - embora isso não atenda ao item (b).
mklement0

Por favor, mostre-me um exemplo de b) onde isso falha. Por definição, um link simbólico quebrado aponta para uma entrada de diretório que não existe. O objetivo deste script é resolver os links simbólicos na outra direção. Se o link simbólico estivesse quebrado, você não estaria executando o script. Este exemplo tem como objetivo demonstrar a resolução do script atualmente em execução.
Dave

"destinado a demonstrar a resolução do script atualmente em execução" - na verdade, que é um estreitamento do escopo da pergunta que você optou por focar; isso é perfeitamente bom, desde que você diga. Desde que você não fez, eu declarei isso no meu comentário. Corrija o problema de cotação, que é um problema, independentemente do escopo da resposta.
mklement0

2

Nota: acredito que seja uma solução sólida, portátil e pronta para uso, que é invariavelmente demorada por esse motivo.

Abaixo está um script / função totalmente compatível com POSIX que, portanto, é multiplataforma (funciona também no macOS, que readlinkainda não suporta a -fpartir da versão 10.12 (Sierra)) - ele usa apenas recursos da linguagem shell POSIX e apenas chamadas de utilitário compatíveis com POSIX .

É uma implementação portátil do GNUreadlink -e (a versão mais rígida do readlink -f).

Você pode executar o script comsh ou fonte a função em bash, kshezsh :

Por exemplo, dentro de um script, você pode usá-lo da seguinte maneira para obter o diretório de origem verdadeiro do script em execução, com os links simbólicos resolvidos:

trueScriptDir=$(dirname -- "$(rreadlink "$0")")

rreadlink definição de script / função:

O código foi adaptado com gratidão a partir desta resposta .
Eu também criei uma bashversão de utilitário independente baseada em aqui , com a qual você pode instalar
npm install rreadlink -g, se o Node.js. estiver instalado.

#!/bin/sh

# SYNOPSIS
#   rreadlink <fileOrDirPath>
# DESCRIPTION
#   Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
#   prints its canonical path. If it is not a symlink, its own canonical path
#   is printed.
#   A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
#   - Won't work with filenames with embedded newlines or filenames containing 
#     the string ' -> '.
# COMPATIBILITY
#   This is a fully POSIX-compliant implementation of what GNU readlink's
#    -e option does.
# EXAMPLE
#   In a shell script, use the following to get that script's true directory of origin:
#     trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  # Try to make the execution environment as predictable as possible:
  # All commands below are invoked via `command`, so we must make sure that
  # `command` itself is not redefined as an alias or shell function.
  # (Note that command is too inconsistent across shells, so we don't use it.)
  # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not 
  # even have an external utility version of it (e.g, Ubuntu).
  # `command` bypasses aliases and shell functions and also finds builtins 
  # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
  # that to happen.
  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

rreadlink "$@"

Uma tangente à segurança:

jarno , em referência à função que garante que o builtin commandnão seja sombreado por uma função de apelido ou shell com o mesmo nome, pergunta em um comentário:

E se unalias ou unsete [são definidos como aliases ou funções de shell?

A motivação por trás de rreadlinkgarantir quecommand tem seu significado original é usá-lo para ignorar aliases de conveniência (benignos) e funções frequentemente usadas para sombrear comandos padrão em shells interativos, como redefinir lspara incluir opções favoritas.

Eu acho que é seguro dizer que, a menos que você esteja lidando com um ambiente malicioso não confiável, se preocupe com unalias ou unset- ou, para essa matéria, while, do, ... - sendo redefinido não é uma preocupação.

Tem algo que a função deve confiar para ter seu significado e comportamento originais - não há como contornar isso.
O fato de os shells do tipo POSIX permitirem a redefinição de componentes internos e até palavras-chave de idioma é inerentemente um risco à segurança (e escrever código paranóico é difícil em geral).

Para resolver suas preocupações especificamente:

A função depende unaliase unsettem seu significado original. Redefini-las como funções shell de uma maneira que altera seu comportamento seria um problema; redefinição como um apelido não é necessariamente uma preocupação, porque citar (parte do) nome do comando (por exemplo, \unalias) ignora os aliases.

No entanto, citando é não uma opção para a Shell palavras-chave ( while, for,if , do, ...) e enquanto palavras-chave shell fazer têm precedência sobre shell funções , em bashe zshaliases têm a mais alta prioridade, de modo a proteger contra redefinições shell-palavra-chave que devem ser executados unaliascom seus nomes (embora em shells não interativos bash (como scripts), os aliases não sejam expandidos por padrão - somente se shopt -s expand_aliasesfor explicitamente chamado primeiro.

Para garantir que unalias- como um componente interno - tenha seu significado original, você deve usá \unset-lo primeiro, o que exige que ele unsettenha seu significado original:

unseté um shell integrado , portanto, para garantir que ele seja chamado como tal, você deve garantir que ele próprio não seja redefinido como uma função . Embora você possa ignorar um formulário de alias com citação, não pode ignorar um formulário de função de shell - captura 22.

Assim, a menos que você possa confiar unsetpara ter seu significado original, pelo que posso dizer, não há maneira garantida de se defender contra todas as redefinições maliciosas.


Na minha experiência, citar ignora aliases, não funções de shell, ao contrário do que você primeiro disse.
Jarno

Testei que pode definir [como um alias em dashe bashe como uma função de escudo na bash.
Jarno

1
Fiz meu argumento como uma pergunta separada .
Jarno

@jarno: bons pontos; Eu atualizei minha resposta; deixe-me saber se você acha que ainda há um problema.
mklement0

1
@jarno: Você pode definir whilecomo uma função em bash, kshe zsh(mas não dash), mas somente com function <name>sintaxe: function while { echo foo; }obras ( while() { echo foo; }não). No entanto, isso não sombreia a while palavra - chave , porque as palavras-chave têm maior precedência que as funções (a única maneira de chamar essa função é como \while). Em bashe zsh, os alias têm precedência mais alta que as palavras-chave; portanto, as redefinições de alias das palavras-chave as sombream (mas, bashpor padrão, apenas em shells interativos , a menos que shopt -s expand_aliasesseja explicitamente chamado).
mklement0

1

Os scripts de shell comuns geralmente precisam encontrar seu diretório "inicial", mesmo que sejam chamados como um link simbólico. O script, portanto, precisa encontrar sua posição "real" a partir de apenas US $ 0.

cat `mvn`

no meu sistema imprime um script contendo o seguinte, que deve ser uma boa dica do que você precisa.

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`

1

Tente o seguinte:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))

1

Desde que eu me deparei com isso muitas vezes ao longo dos anos, e desta vez eu precisava de uma versão portátil pura do bash que pudesse usar no OSX e no linux, fui em frente e escrevi uma:

A versão viva mora aqui:

https://github.com/keen99/shell-functions/tree/master/resolve_path

mas para o SO, aqui está a versão atual (acho que está bem testada ... mas estou aberta a comentários!)

Pode não ser difícil fazê-lo funcionar com casca de bourne comum (sh), mas eu não tentei ... Eu gosto muito de $ FUNCNAME. :)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

aqui está um exemplo clássico, graças ao brew:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

use esta função e retornará o caminho -real-:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn 

Minha resposta anterior faz exatamente a mesma coisa em cerca de 1/4 do espaço :) É um script de shell bastante básico e não é digno de um repositório git.
Dave

1

Para contornar a incompatibilidade do Mac, eu vim com

echo `php -r "echo realpath('foo');"`

Não é ótimo, mas cross OS


3
O Python 2.6+ está disponível em mais sistemas de usuários finais que o php, portanto python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));", provavelmente faria mais sentido. Ainda assim, quando você precisar usar o Python a partir de um script de shell, provavelmente deverá usar o Python e poupar a dor de cabeça dos scripts de shell de plataforma cruzada.
Jonathan Baldwin

Você não precisa mais do que o link de leitura embutido sh, o nome do diretório e o nome da base.
Dave

@ Dave: dirname, basename, e readlinksão externos utilitários , não shell embutidos; dirnamee basenamefazem parte do POSIX, readlinknão é.
precisa saber é o seguinte

@ mklement0 - Você está certo. Eles são fornecidos pelo CoreUtils ou equivalente. Eu não deveria visitar SO depois da 01:00. A essência do meu comentário é que nem PHP nem qualquer outro interpretador de linguagem além do instalado em um sistema básico são necessários. Eu usei o script que forneci nesta página em todas as variantes do Linux desde 1997 e no MacOS X desde 2006 sem erros. O OP não pediu uma solução POSIX. Seu ambiente específico era Mac OS X.
Dave

@ Dave: Sim, é possível fazê-lo com utilitários de ações, mas também é difícil fazê-lo (como evidenciado pelas deficiências do seu script). Se o OS X era realmente o foco, então essa resposta é perfeitamente correta - e muito mais simples -, uma vez que phpvem com o OS X. No entanto, mesmo que o corpo da pergunta mencione o OS X, ele não está marcado como tal e ficou claro que as pessoas em várias plataformas vêm aqui para obter respostas, então vale a pena apontar o que é específico da plataforma / não é POSIX.
mklement0

1

Este é um resolvedor de link simbólico no Bash que funciona se o link é um diretório ou não-diretório:

function readlinks {(
  set -o errexit -o nounset
  declare n=0 limit=1024 link="$1"

  # If it's a directory, just skip all this.
  if cd "$link" 2>/dev/null
  then
    pwd -P
    return 0
  fi

  # Resolve until we are out of links (or recurse too deep).
  while [[ -L $link ]] && [[ $n -lt $limit ]]
  do
    cd "$(dirname -- "$link")"
    n=$((n + 1))
    link="$(readlink -- "${link##*/}")"
  done
  cd "$(dirname -- "$link")"

  if [[ $n -ge $limit ]]
  then
    echo "Recursion limit ($limit) exceeded." >&2
    return 2
  fi

  printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}

Observe que todas as coisas cde setacontecem em um subshell.


Na verdade, os {} ao redor de () são desnecessários, pois () conta como um "comando composto", assim como {}. Mas você ainda precisa () após o nome da função.
Chris Cogdon

@ChrisCogdon A {}volta ao redor ()não é desnecessária se não houver ()por trás do nome da função. O Bash aceita declarações de funções sem, ()porque o shell não possui listas de parâmetros nas declarações e não faz chamadas com ()isso; as declarações de funções com ()não fazem muito sentido.
solidsnack

0

Aqui, apresento o que acredito ser uma solução de plataforma cruzada (Linux e macOS pelo menos) à resposta que está funcionando bem para mim atualmente.

crosspath()
{
    local ref="$1"
    if [ -x "$(which realpath)" ]; then
        path="$(realpath "$ref")"
    else
        path="$(readlink -f "$ref" 2> /dev/null)"
        if [ $? -gt 0 ]; then
            if [ -x "$(which readlink)" ]; then
                if [ ! -z "$(readlink "$ref")" ]; then
                    ref="$(readlink "$ref")"
                fi
            else
                echo "realpath and readlink not available. The following may not be the final path." 1>&2
            fi
            if [ -d "$ref" ]; then
                path="$(cd "$ref"; pwd -P)"
            else
                path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
            fi
        fi
    fi
    echo "$path"
}

Aqui está uma solução do macOS (apenas?). Possivelmente melhor adaptado à pergunta original.

mac_realpath()
{
    local ref="$1"
    if [[ ! -z "$(readlink "$ref")" ]]; then
        ref="$(readlink "$1")"
    fi
    if [[ -d "$ref" ]]; then
        echo "$(cd "$ref"; pwd -P)"
    else
        echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
    fi
}

0

Minha resposta aqui Bash: como obter o caminho real de um link simbólico?

mas, em suma, muito útil nos scripts:

script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home

Eles fazem parte do núcleo GNU, adequado para uso em sistemas Linux.

Para testar tudo, colocamos o link simbólico em / home / test2 /, alteramos algumas coisas adicionais e executamos / chamamos a partir do diretório raiz:

/$ /home/test2/symlink
/home/test
Original script home: /home/test

Onde

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink

0

Caso o pwd não possa ser usado (por exemplo, chamando scripts de um local diferente), use realpath (com ou sem dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Funciona tanto ao chamar (vários) links simbólicos quanto ao chamar diretamente o script - de qualquer local.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.