ti-enxame.com

Como você normaliza um caminho de arquivo no Bash?

Eu quero transformar /foo/bar/.. para /foo

Existe um comando bash que faz isso?


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

164
Fabien

se você quiser fazer 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 alternatives

Se realpath não for suportado pelo seu Shell, você pode tentar 

readlink -f /path/here/.. 

Além disso

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

Funciona da mesma 

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

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

161
Kent Fredric

Eu não sei se existe um comando bash direto para fazer isso, mas eu geralmente faço

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

e funciona bem.

86
Tim Whitcomb

Tente realpath. Abaixo está a fonte em sua totalidade, por meio deste 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);
}
53
Adam Liss

Uma solução portátil e confiável é usar python, que é pré-instalado em praticamente todos os lugares (incluindo o 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 fazê-lo, resolve os 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/file pode ser um caminho relativo ou absoluto.

34
loevborg

Use o utilitário readlink do pacote coreutils.

MY_PATH=$(readlink -f "$0")
34
mattalxndr

readlink é o padrão básico para obter o caminho absoluto. Ele também tem a vantagem de retornar strings vazias se não existirem caminhos ou um caminho (dadas as sinalizações para isso).

Para obter o caminho absoluto para um diretório que pode ou não existir, mas cujos pais existem, 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 dado e seguir links simbólicos se eles existirem, mas caso contrário, ignore os diretórios ausentes e apenas retorne o caminho de qualquer maneira, é:

abspath=$(readlink -m $path)

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

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

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

13
Craig

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

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

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

Duas variações, supondo que seus 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 ) "#logical preservando links simbólicos 

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

FYI: variação na obtenção do diretório inicial de um script que funciona mesmo se o script alterar seu diretório atual mais tarde.

name0 = "$ (basename" $ ​​0 ")"; #base nome do script 
 dir0 = "$ (cd" $ (dirname "$ 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 dá apenas ... Inútil se o script fizer um cd mais tarde. 

7
Gilbert

Como Adam Liss observou, realpath não é fornecido com todas as distribuições. O que é uma pena, porque é a melhor solução. O código fonte fornecido é ótimo e provavelmente começarei a usá-lo agora. Aqui está o que eu tenho usado até agora, o qual eu compartilho aqui apenas por completo:

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ê quiser resolver links simbólicos, basta substituir pwd por pwd -P.

7
Jeet

Minha solução recente foi:

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

Baseado na resposta de Tim Whitcomb.

7
schmunk

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

readlink é bom se você realmente quiser seguir links simbólicos. Mas há também um caso de uso para simplesmente normalizar as seqüências ./ e ../ e //, o que pode ser feito de forma puramente sintática, sem canonizando links simbólicos. readlink não é bom para isso, e também não é realpath.

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

funciona para caminhos existentes, mas quebra para outros.

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

FWIW, um one-liner 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
5
Jesse Glick

Uma resposta faladora e um pouco atrasada. Eu preciso escrever uma vez que estou preso no RHEL4/5 mais antigo. Eu ligo 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`
4
alhernau

Estou atrasado para a festa, mas esta é a solução que eu criei depois de ler um monte de 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, jogue Nice com ~, mantenha os links simbólicos no caminho onde estão e isso não vai atrapalhar sua pilha de diretórios. Ele 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.

4
apottere

Experimente o nosso novo produto de biblioteca Bash realpath-lib que colocamos no GitHub para uso livre e sem restrições. Está completamente documentado e é uma ótima ferramenta de aprendizado. 

Ele resolve os caminhos locais, relativos e absolutos e não possui nenhuma dependência, exceto o Bash 4+; por isso 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

}

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.

3
AsymLabs

Com base na resposta do @ Andre, talvez eu tenha uma versão um pouco melhor, caso alguém esteja atrás de uma solução baseada em loop e completamente manipulada por strings. Também é útil para aqueles que não querem desreferenciar quaisquer links simbólicos, que é a desvantagem de usar realpath ou readlink -f.

Ele funciona nas versões 3.2.25 e superiores do bash.

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
2
ϹοδεMεδιϲ

O problema com 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 esta variante também faz not requer que o caminho exista.

1
André Anjos

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

  • Trabalhe em um Mac de estoque. realpath e readlink -f são addons
  • Resolver links simbólicos
  • Tem tratamento de erro

Nenhuma das respostas tinha os dois e dois. Eu adicionei # 3 para salvar os outros mais de barbear yak.

#!/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 pequeno caso de teste com alguns espaços torcidos nos caminhos para exercitar totalmente 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 "
0
David Blevins

Baseado no excelente trecho python do loveborg, eu escrevi o seguinte:

#!/bin/sh

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

for file in "[email protected]"; 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
0
Edward Falk
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

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

0
user240515

Eu sei que esta é uma questão antiga. Eu ainda estou oferecendo uma alternativa. Recentemente eu encontrei o mesmo problema e não encontrei nenhum comando existente e portátil para fazer isso. Então eu escrevi o seguinte script 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

0
bestOfSong