Como você normaliza um caminho de arquivo no Bash?

204

Eu quero me transformar /foo/bar/..em/foo

Existe um comando bash que faz isso?


Edit: no meu caso prático, o diretório existe.

Fabien
fonte
4
Importa se existe /foo/barou /foorealmente existe, ou você está interessado apenas no aspecto de manipulação de cadeias de acordo com as regras de nome de caminho?
Chen Levy
5
@twalberg ... que é inventado meio ...
Camilo Martin
2
@CamiloMartin não artificial em tudo - ele faz exatamente o que a pergunta pede - transforma /foo/bar/..a /foo, e usando um bashcomando. Se houver outros requisitos que não sejam reconhecidos, então talvez eles devem ser ...
twalberg
14
@twalberg Você tem feito muito TDD -_- '
Camilo Martin

Respostas:

193

se você deseja remover parte de um nome de arquivo do caminho, "dirname" e "basename" são seus amigos, e "realpath" também é útil.

dirname /foo/bar/baz 
# /foo/bar 
basename /foo/bar/baz
# baz
dirname $( dirname  /foo/bar/baz  ) 
# /foo 
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp

realpath alternativas

Se realpathnão for suportado pelo seu shell, você pode tentar

readlink -f /path/here/.. 

Além disso

readlink -m /path/there/../../ 

Funciona da mesma forma que

realpath -s /path/here/../../

em que o caminho não precisa existir para ser normalizado.

Kent Fredric
fonte
5
Para aqueles que precisam de uma solução para o OS X, confira a resposta de Adam Liss abaixo.
Trenton
stackoverflow.com/a/17744637/999943 Esta é uma resposta fortemente relacionada! Me deparei com as duas postagens de controle de qualidade em um dia e queria vinculá-las.
Phyatt
realpath parece ter sido adicionado ao coreutils em 2012. Consulte o histórico do arquivo em github.com/coreutils/coreutils/commits/master/src/realpath.c .
Noah Lavine
1
Ambos realpathe readlinkvem dos utilitários principais do GNU, então provavelmente você obtém os dois ou nenhum. Se bem me lembro, a versão readlink no Mac é um pouco diferente da versão GNU: - \
Antoine 'hashar' Musso
100

Não sei se existe um comando direto do bash para fazer isso, mas geralmente faço

normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"

e funciona bem.

Tim Whitcomb
fonte
9
Isso normalizará, mas não resolverá os links programáveis. Isso pode ser um bug ou um recurso. :-)
Adam Liss
4
Também tem um problema se $ CDPATH estiver definido; porque o "cd foo" mudará para qualquer diretório "foo" que seja um subdiretório de $ CDPATH, não apenas um "foo" que está no diretório atual. Eu acho que você precisa fazer algo como: CDPATH = "" cd "$ {dirToNormalize}" && pwd -P.
MJS
7
A resposta de Tim é definitivamente a mais simples e mais portátil. CDPATH é fácil de lidar: dir = "$ (CDPATH não definido && cd" $ dir "&& pwd)"
David Blevins
2
Isso pode ser muito perigoso ( rm -rf $normalDir) se dirToNormalize não existir!
Frank Meulenaar
4
Sim, provavelmente é melhor usar um &&comentário do @ DavidBlevins.
Elias Dorneles
54

Tente realpath. Abaixo está a fonte na íntegra, doada ao domínio público.

// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>   
#include <limits.h>

static char *s_pMyName;
void usage(void);

int main(int argc, char *argv[])
{
    char
        sPath[PATH_MAX];


    s_pMyName = strdup(basename(argv[0]));

    if (argc < 2)
        usage();

    printf("%s\n", realpath(argv[1], sPath));
    return 0;
}    

void usage(void)
{
    fprintf(stderr, "usage: %s PATH\n", s_pMyName);
    exit(1);
}
Adam Liss
fonte
1
Isso está incluído como parte da instalação padrão do bash? Estou ficando "comando não encontrado" em nosso sistema (bash 3.00.15 (1) no RHEL)
Tim Whitcomb
4
gnu.org/software/coreutils para readlink, mas realpath vem deste pacote: packages.debian.org/unstable/utils/realpath
Kent Fredric
8
É padrão no Ubuntu / BSD, não Centos / OSX
Erik Aronesty
4
Você realmente deve encontrar a parte com "Bônus: é um comando bash e disponível em todos os lugares!" parte sobre realpath. Também é o meu favorito, mas em vez de complementar sua ferramenta da fonte. É tudo muito pesado, e na maioria das vezes você só quer readlink -f... BTW, readlinktambém NÃO é um bash embutido, mas faz parte do coreutilsUbuntu.
Tomasz Gandor
4
Você disse que está doando isso para o domínio público, mas o cabeçalho também diz "para qualquer uso sem fins lucrativos" - você realmente pretende doá-lo para o domínio público? Isso significaria que pode ser usado para fins comerciais com fins lucrativos, bem como para organizações sem fins lucrativos ...
me_e
36

Uma solução portátil e confiável é usar python, que é pré-instalado em praticamente todos os lugares (incluindo Darwin). Você tem duas opções:

  1. abspath retorna um caminho absoluto, mas não resolve links simbólicos:

    python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

  2. realpath retorna um caminho absoluto e, ao fazer isso, resolve links simbólicos, gerando um caminho canônico:

    python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file

Em cada caso, path/to/filepode ser um caminho relativo ou absoluto.

Loevborg
fonte
7
Obrigado, esse foi o único que funcionou. O readlink ou o caminho real não estão disponíveis no OS X. O Python deve estar na maioria das plataformas.
sorin
1
Só para esclarecer, readlinkestá disponível no OS X, mas não com a -fopção. Soluções portáteis discutidas aqui .
StvnW
3
Literalmente me surpreende que essa seja a única solução sã se você não quiser seguir os links. A maneira Unix meu pé.
precisa saber é o seguinte
34

Use o utilitário readlink do pacote coreutils.

MY_PATH=$(readlink -f "$0")
mattalxndr
fonte
1
O BSD não possui o sinalizador -f, o que significa que isso falhará mesmo no MacOS Mojave mais recente e em muitos outros sistemas. Esqueça o uso de -f se você quiser portabilidade, muitos sistemas operacionais são afetados.
sorin
1
@sorin. A questão não é sobre Mac, é sobre Linux.
mattalxndr
14

readlinké o padrão do bash para obter o caminho absoluto. Ele também tem a vantagem de retornar cadeias vazias se não houver caminhos ou um caminho (considerando os sinalizadores para isso).

Para obter o caminho absoluto para um diretório que pode ou não existir, mas quem são os pais, use:

abspath=$(readlink -f $path)

Para obter o caminho absoluto para um diretório que deve existir junto com todos os pais:

abspath=$(readlink -e $path)

Para canonizar o caminho fornecido e seguir os links simbólicos, se eles existirem, mas ignorar os diretórios ausentes e retornar o caminho de qualquer maneira, é:

abspath=$(readlink -m $path)

A única desvantagem é que o readlink seguirá os links. Se você não deseja seguir os links, pode usar esta convenção alternativa:

abspath=$(cd ${path%/*} && echo $PWD/${path##*/})

Isso irá chdir para a parte do diretório de $ path e imprimir o diretório atual junto com a parte do arquivo de $ path. Se não conseguir chdir, você obtém uma string vazia e um erro no stderr.

Craig
fonte
7
readlinké uma boa opção se estiver disponível. A versão do OS X não suporta as opções -eou -f. Nos três primeiros exemplos, você deve $pathcolocar aspas duplas para manipular espaços ou curingas no nome do arquivo. +1 para expansão de parâmetros, mas isso tem uma vulnerabilidade de segurança. Se pathestiver vazio, isso será direcionado cdpara o seu diretório pessoal. Você precisa de aspas duplas. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
toxalot
Este foi apenas um exemplo. Se você está interessado em segurança, não deve usar o bash ou qualquer outra variante do shell. Além disso, o bash tem seus próprios problemas quando se trata de compatibilidade entre plataformas, além de ter problemas com a alteração de funcionalidade entre as principais versões. O OSX é apenas uma das muitas plataformas com problemas pertinentes ao script de shell, sem mencionar que é baseado no BSD. Quando você precisa ser verdadeiramente multiplataforma, precisa estar em conformidade com o POSIX, para que a expansão dos parâmetros realmente saia da janela. Dê uma olhada no Solaris ou HP-UX algum tempo.
Craig
6
Não significando nenhuma ofensa aqui, mas apontar questões obscuras como essa é importante. Eu só estou querendo uma resposta rápida para esse problema trivial e eu teria confiado nesse código com qualquer entrada / todas, se não fosse o comentário acima. Também é importante oferecer suporte ao OS-X nessas discussões básicas. Infelizmente, existem muitos comandos que não são suportados no OS-X, e muitos fóruns tomam isso como garantido ao discutir o Bash, o que significa que continuaremos recebendo muitos problemas de plataforma cruzada, a menos que sejam tratados mais cedo ou mais tarde.
Rebs 5/05
13

Pergunta antiga, mas há uma maneira muito mais simples se você estiver lidando com nomes de caminhos completos no nível do shell:

   abspath = "$ (cd" $ path "&& pwd)"

Como o CD acontece em um subshell, não afeta o script principal.

Duas variações, supondo que os comandos internos do shell aceitem -L e -P, são:

   abspath = "$ (cd -P" $ path "&& pwd -P)" # caminho físico com links simbólicos resolvidos
   abspath = "$ (cd -L" $ path "&& pwd -L)" # caminho lógico preservando links simbólicos

Pessoalmente, raramente preciso dessa abordagem posterior, a menos que esteja fascinado com links simbólicos por algum motivo.

FYI: variação na obtenção do diretório inicial de um script que funciona mesmo que o script altere seu diretório atual posteriormente.

name0 = "$ (nome da base" $ 0 ")"; #base nome do script
dir0 = "$ (cd" $ (nome do diretório "$ 0") "&& pwd)"; #absolute começando dir

O uso do CD garante que você sempre tenha o diretório absoluto, mesmo que o script seja executado por comandos como ./script.sh que, sem o cd / pwd, geralmente fornecem apenas .. Inútil se o script gravar um CD posteriormente.

Gilbert
fonte
8

Como observou Adam Liss, realpathnão está incluído em todas as distribuições. O que é uma pena, porque é a melhor solução. O código fonte fornecido é ótimo e provavelmente vou começar a usá-lo agora. Aqui está o que eu tenho usado até agora, que eu compartilho aqui apenas para completar:

get_abs_path() {
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
} 

Se você deseja resolver links simbólicos, substitua pwdpor pwd -P.

Jeet
fonte
Uma dica com a pwd -Popção para este caso ... Considere o que aconteceria se $(basename "$1")fosse um link simbólico para um arquivo em outro diretório. O pwd -Púnico resolve links simbólicos na parte do diretório do caminho, mas não na parte do nome da base.
toxalot
7

Minha solução recente foi:

pushd foo/bar/..
dir=`pwd`
popd

Com base na resposta de Tim Whitcomb.

schmunk
fonte
Eu suspeito que isso falhe se o argumento não for um diretório. Suponha que eu queira saber para onde / usr / bin / java leva?
Edward Falk,
1
Se você souber que é um arquivo, pushd $(dirname /usr/bin/java)tente.
schmunk
5

Não é exatamente uma resposta, mas talvez uma pergunta de acompanhamento (a pergunta original não foi explícita):

readlinké bom se você realmente deseja seguir links simbólicos. Mas há também um caso de uso para apenas normalizando ./e ../e //seqüências, que pode ser feito puramente sintática, sem Canonicalização links simbólicos. readlinknão é bom para isso, e nem é realpath.

for f in $paths; do (cd $f; pwd); done

funciona para caminhos existentes, mas quebra para outros.

Um sedscript parece ser uma boa aposta, exceto que você não pode substituir iterativamente as seqüências ( /foo/bar/baz/../..-> /foo/bar/..-> /foo) sem usar algo como Perl, que não é seguro assumir em todos os sistemas, ou usar um loop feio para comparar a saída desed da sua entrada.

FWIW, uma linha usando Java (JDK 6+):

jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths
Jesse Glick
fonte
realpathtem uma -sopção para não resolver links simbólicos e apenas referências determinação de /./, /../e remover extras /caracteres. Quando combinado com a -mopção, realpathopera apenas no nome do arquivo e não toca em nenhum arquivo real. Ele soa como a solução perfeita. Mas, infelizmente, realpathainda está faltando em muitos sistemas.
toxalot
A remoção de ..componentes não pode ser feita sintaticamente quando links simbólicos estão envolvidos. /one/two/../threenão é o mesmo /one/threeque twoum link simbólico para /foo/bar.
jrw32982 suporta Monica
@ jrw32982 sim, como eu disse na minha resposta, isso é para um caso de uso em que a canonização do link simbólico não é desejada ou necessária.
Jesse Glick #
@JesseGlick não se trata apenas de você querer ou não canonizar links simbólicos. Seu algoritmo realmente produz a resposta errada. Para que sua resposta seja correta, você precisa saber a priori que não havia links simbólicos envolvidos (ou que eles eram apenas de uma determinada forma). Sua resposta diz que você não deseja canonizá-los, não que não haja links simbólicos envolvidos no caminho.
Jrw32982 suporta Monica
Há casos de uso em que a normalização deve ser executada sem assumir nenhuma estrutura de diretório fixa e existente. A normalização do URI é semelhante. Nesses casos, é uma limitação inerente que o resultado geralmente não esteja correto se houver links simbólicos perto de um diretório em que o resultado será aplicado posteriormente.
perfil completo de Jesse Glick
5

Estou atrasado para a festa, mas esta é a solução que eu criei depois de ler vários tópicos como este:

resolve_dir() {
        (builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}

Isso resolverá o caminho absoluto de $ 1, será agradável com ~, manterá os links simbólicos no caminho em que estão e não mexerá com sua pilha de diretórios. Retorna o caminho completo ou nada se não existir. Ele espera que $ 1 seja um diretório e provavelmente falhará, se não for, mas essa é uma verificação fácil de se fazer.

apottere
fonte
4

Falador, e resposta um pouco tarde. Preciso escrever um, pois estou preso no RHEL4 / 5 mais antigo. Lida com links absolutos e relativos e simplifica as entradas //, /./ e somedir /../.

test -x /usr/bin/readlink || readlink () {
        echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
    }


test -x /usr/bin/realpath || realpath () {
    local PATH=/bin:/usr/bin
    local inputpath=$1
    local changemade=1
    while [ $changemade -ne 0 ]
    do
        changemade=0
        local realpath=""
        local token=
        for token in ${inputpath//\// }
        do 
            case $token in
            ""|".") # noop
                ;;
            "..") # up one directory
                changemade=1
                realpath=$(dirname $realpath)
                ;;
            *)
                if [ -h $realpath/$token ] 
                then
                    changemade=1
                    target=`readlink $realpath/$token`
                    if [ "${target:0:1}" = '/' ]
                    then
                        realpath=$target
                    else
                        realpath="$realpath/$target"
                    fi
                else
                    realpath="$realpath/$token"
                fi
                ;;
            esac
        done
        inputpath=$realpath
    done
    echo $realpath
}

mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`
alhernau
fonte
3

Experimente nosso novo produto da biblioteca Bash, realpath-lib, que colocamos no GitHub para uso gratuito e sem ônus. Está completamente documentado e é uma ótima ferramenta de aprendizado.

Ele resolve caminhos locais, relativos e absolutos e não possui nenhuma dependência, exceto o Bash 4+; então deve funcionar em qualquer lugar. É grátis, limpo, simples e instrutivo.

Você pode fazer:

get_realpath <absolute|relative|symlink|local file path>

Esta função é o núcleo da biblioteca:

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

}

Ele também contém funções para get_dirname, get_filename, get_ stemname e validate_path. Experimente em várias plataformas e ajude a melhorá-lo.

AsymLabs
fonte
2

O problema realpathé que ele não está disponível no BSD (ou no OSX). Aqui está uma receita simples extraída de um artigo bastante antigo (2009) do Linux Journal , que é bastante portátil:

function normpath() {
  # Remove all /./ sequences.
  local path=${1//\/.\//\/}

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}

Observe que essa variante também não requer que o caminho exista.

André Anjos
fonte
No entanto, isso não resolve os links simbólicos. O caminho real processa os caminhos que começam na raiz e seguem os links simbólicos à medida que progridem. Tudo o que isso faz é recolher as referências pai.
Martijn Pieters
2

Com base na resposta de @ Andre, talvez eu tenha uma versão um pouco melhor, caso alguém esteja atrás de uma solução sem manipulação de loop e completamente baseada em manipulação de string. Também é útil para aqueles que não querem desferir nenhum link simbólico, que é a desvantagem de usar realpathor readlink -f.

Funciona nas versões bash 3.2.25 e superior.

shopt -s extglob

normalise_path() {
    local path="$1"
    # get rid of /../ example: /one/../two to /two
    path="${path//\/*([!\/])\/\.\./}"
    # get rid of /./ and //* example: /one/.///two to /one/two
    path="${path//@(\/\.\/|\/+(\/))//}"
    # remove the last '/.'
    echo "${path%%/.}"
}

$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config
ϹοδεMεδιϲ
fonte
É uma boa idéia, mas perdi 20 minutos tentando fazer com que isso funcionasse em várias versões diferentes do bash. Acontece que a opção extglob shell precisa estar ativada para que isso funcione, e não é por padrão. Quando se trata da funcionalidade do bash, é importante especificar a versão necessária e as opções não padrão, porque esses detalhes podem variar entre os sistemas operacionais. Por exemplo, uma versão recente do Mac OSX (Yosemite) vem apenas com uma versão desatualizada do bash (3.2).
Drwatsoncode
Desculpe @ricovox; Eu já atualizei isso. Estou ansioso para saber a versão exata do Bash que você tem lá. A fórmula acima (atualizada) funciona no CentOS 5.8, que vem com o bash 3.2.25
ϹοδεMεδιϲ
Desculpe pela confusão. Este código funcionou na minha versão do Mac OSX bash (3.2.57) depois de ativar o extglob. Minha observação sobre as versões do bash era mais geral (que na verdade se aplica mais a outra resposta aqui sobre regex no bash).
Drwatsoncode 13/10/2015
2
Agradeço sua resposta. Eu usei como base para mim. Aliás, notei vários casos em que o seu falha: (1) Caminhos relativos hello/../world(2) Pontos no nome do arquivo /hello/..world(3) Pontos após barra dupla /hello//../world(4) Ponto antes ou após barra dupla /hello//./worldou /hello/.//world (5) Pai após corrente: /hello/./../world/ (6) Pai after Parent:, /hello/../../worldetc. - Alguns desses podem ser corrigidos usando um loop para fazer correções até que o caminho pare de mudar. (Também remover dir/../, não /dir/..mas remover dir/..a partir da extremidade.)
drwatsoncode
0

Baseado no excelente snippet python do loveborg, escrevi o seguinte:

#!/bin/sh

# Version of readlink that follows links to the end; good for Mac OS X

for file in "$@"; do
  while [ -h "$file" ]; do
    l=`readlink $file`
    case "$l" in
      /*) file="$l";;
      *) file=`dirname "$file"`/"$l"
    esac
  done
  #echo $file
  python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done
Edward Falk
fonte
0
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

Isso funciona mesmo se o arquivo não existir. Exige que o diretório que contém o arquivo exista.

user240515
fonte
O GNU Realpath também não requer que o último elemento do caminho exista, a menos que você o userealpath -e
Martijn Pieters
0

Eu precisava de uma solução que fizesse todos os três:

  • Trabalhe em um Mac padrão. realpathe readlink -fsão addons
  • Resolver links simbólicos
  • Tenha manipulação de erro

Nenhuma das respostas tinha os números 1 e 2. Adicionei o número 3 para salvar outras pessoas.

#!/bin/bash

P="${1?Specify a file path}"

[ -e "$P" ] || { echo "File does not exist: $P"; exit 1; }

while [ -h "$P" ] ; do
    ls="$(ls -ld "$P")"
    link="$(expr "$ls" : '.*-> \(.*\)$')"
    expr "$link" : '/.*' > /dev/null &&
        P="$link" ||
        P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"

Aqui está um breve caso de teste com alguns espaços distorcidos nos caminhos para exercitar completamente a citação

mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "

cd  "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "
David Blevins
fonte
0

Eu sei que esta é uma pergunta antiga. Eu ainda estou oferecendo uma alternativa. Recentemente, encontrei o mesmo problema e não encontrei nenhum comando portátil e existente para fazer isso. Então, eu escrevi o seguinte script de shell, que inclui uma função que pode fazer o truque.

#! /bin/sh                                                                                                                                                

function normalize {
  local rc=0
  local ret

  if [ $# -gt 0 ] ; then
    # invalid
    if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
      echo $1
      return -1
    fi

    # convert to absolute path
    if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
      normalize "`pwd`/$1"
      return $?
    fi

    ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
  else
    read line
    normalize "$line"
    return $?
  fi

  if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
    ret=`normalize "$ret"`
    rc=$?
  fi

  echo "$ret"
  return $rc
}

https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c

bestOfSong
fonte
0

Eu criei uma função exclusiva para lidar com isso, com foco no desempenho mais alto possível (por diversão). Ele não resolve os links simbólicos, portanto é basicamente o mesmo que realpath -sm.

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () { 
  ${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "${s:-/}";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;
}

Nota: Esta função não trata de caminhos iniciados com //, pois exatamente duas barras duplas no início de um caminho são um comportamento definido pela implementação. No entanto, ele lida com /,/// e assim por diante muito bem.

Essa função parece lidar com todos os casos extremos corretamente, mas ainda pode haver alguns por aí que eu não lidei.

Nota de desempenho: quando chamado com milhares de argumentos, abspathroda cerca de 10x mais lentamente que realpath -sm; quando chamado com um único argumento, abspathexecuta> 110x mais rápido do que realpath -smna minha máquina, principalmente devido à não necessidade de executar um novo programa todas as vezes.

Jeffrey Cash
fonte
-1

Descobri hoje que você pode usar o statcomando para resolver caminhos.

Portanto, para um diretório como "~ / Documents":

Você pode executar isso:

stat -f %N ~/Documents

Para obter o caminho completo:

/Users/me/Documents

Para links simbólicos, você pode usar a opção de formato% Y:

stat -f %Y example_symlink

O que pode retornar um resultado como:

/usr/local/sbin/example_symlink

As opções de formatação podem ser diferentes em outras versões do * NIX, mas funcionaram para mim no OSX.

coldlogic
fonte
1
a stat -f %N ~/Documentslinha é um arenque vermelho ... seu shell está substituindo ~/Documentspor /Users/me/Documentse statestá imprimindo seu argumento literalmente.
danwyand
-4

Uma solução simples usando node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
Artisan72
fonte