Как нормализовать путь к файлу в Bash?

Я хочу, чтобы преобразовать /foo/bar/.. до /foo

есть ли команда bash, которая делает это?


Edit: в моем практическом случае каталог существует.

21 ответов


если вы хотите проглотить часть имени файла из пути, "dirname" и "basename" - ваши друзья, и "realpath" тоже удобен.

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 варианты

если realpath не поддерживается вашей оболочки, вы можете попробовать

readlink -f /path/here/.. 

и

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

работает так же, как

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

в том, что путь не должен существовать, чтобы быть нормализован.


Я не знаю, есть ли команды bash для этого, но я обычно делаю

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

и он работает хорошо.


попробовать realpath. Ниже приведен источник в полном объеме, настоящим передан в общественное достояние.

// 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);
}

используйте утилиту readlink из пакета coreutils.

MY_PATH=$(readlink -f "")

портативным и надежным решением является использование python, который предустановлен практически везде (включая Darwin). У вас есть два варианта:

  1. abspath возвращает абсолютный путь, но не разрешает символические ссылки:

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

  2. realpath возвращает абсолютный путь и при этом разрешает символические ссылки, генерируя канонический путь:

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

в каждом случае, path/to/file может быть либо относительный, либо абсолютный путь.


readlink является стандартом bash для получения абсолютного пути. Он также имеет преимущество возвращать пустые строки, Если пути или путь не существуют (учитывая флаги для этого).

чтобы получить абсолютный путь к каталогу, который может или не может существовать, но кто родители существуют, использовать:

abspath=$(readlink -f $path)

чтобы получить абсолютный путь к каталогу, который должен существовать вместе со всеми родителями:

abspath=$(readlink -e $path)

в canonicalise данный путь и следовать по символическим ссылкам, если они случается существовать, но в противном случае игнорируйте отсутствующие каталоги и просто возвращайте путь в любом случае, это:

abspath=$(readlink -m $path)

единственный недостаток заключается в том, что более ранних версий будут переходить по ссылкам. Если вы не хотите следовать ссылкам, вы можете использовать это альтернативное соглашение:

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

это будет chdir в часть каталога $path и распечатать текущий каталог вместе с частью файла $path. Если это не удается chdir, вы получаете пустую строку и ошибку на stderr.


мое последнее решение было:

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

на основе ответа Тима Уиткомба.


старый вопрос, но есть более простой способ если вы имеете дело с полными именами пути на уровне оболочки:

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

поскольку компакт-диск происходит в подсетке, он не влияет на основной скрипт.

два варианта, Предположим, ваша оболочка встроенные команды accept-L и-P, являются:

   abspath="$( cd -P "$path" && pwd -P )"    #physical path with resolved symlinks
   abspath="$( cd -L "$path" && pwd -L )"    #logical path preserving symlinks

лично мне редко нужен этот более поздний подход, если я не очарован символическими ссылками по какой-то причине.

FYI: изменение на получать начальный каталог скрипта, который работает, даже если скрипт изменяет текущий каталог позже.

name0="$(basename "")";                  #base name of script
dir0="$( cd "$( dirname "" )" && pwd )"; #absolute starting dir

использование CD гарантирует, что у вас всегда есть абсолютный каталог, даже если скрипт запускается такими командами, как ./script.sh который, без cd /pwd, часто дает просто.. Бесполезно, если скрипт сделает cd позже.


как отметил Адам Лисс realpath не в комплекте с каждым дистрибутивом. Жаль, потому что это лучшее решение. Предоставленный исходный код отличный, и я, вероятно, начну использовать его сейчас. Вот то, что я использовал до сих пор, что я разделяю здесь только для полноты:

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

если вы хотите, чтобы он разрешал символические ссылки, просто замените pwd С pwd -P.


разговорчивый, и немного поздний ответ. Мне нужно написать один, так как я застрял на старом RHEL4/5. I обрабатывает абсолютные и относительные ссылки и упрощает //,/./ и somedir/../ вступления.

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


test -x /usr/bin/realpath || realpath () {
    local PATH=/bin:/usr/bin
    local inputpath=
    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`

не совсем ответ, но, возможно, последующий вопрос (первоначальный вопрос не был явным):

readlink отлично, если вы действительно хотите следовать символическим ссылкам. Но есть также вариант использования для простой нормализации ./ и ../ и // последовательности, которые могут быть выполнены чисто синтаксически,без канонизация символических ссылок. readlink не годится для этого, и ни realpath.

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

работает для существующих путей, но и перерывы для другие.

A sed сценарий, казалось бы, хорошая ставка, за исключением того, что вы не можете повторно заменить последовательностей (/foo/bar/baz/../.. ->/foo/bar/.. ->/foo) без использования чего-то вроде Perl, что небезопасно предполагать на всех системах, или используя какой-то уродливый цикл для сравнения вывода sed на его вход.

FWIW, однострочный с использованием 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

Я опаздываю на вечеринку, но это решение, которое я создал после чтения кучки таких потоков:

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

это разрешит абсолютный путь $1, сыграет хорошо с~, сохранит символические ссылки в пути, где они есть, и он не будет связываться с вашим стеком каталогов. Он возвращает полный путь или ничего, если он не существует. Он ожидает, что $1 будет каталогом и, вероятно, потерпит неудачу, если это не так, но это легко проверить самостоятельно.


попробуйте наш новый продукт библиотеки Bash realpath-lib что мы разместили на GitHub для свободного и свободного использования. Это тщательно документировано и является отличным инструментом обучения.

Он разрешает локальные, относительные и абсолютные пути и не имеет никаких зависимостей, кроме Bash 4+; поэтому он должен работать практически в любом месте. Это бесплатно, чисто, просто и поучительно.

Вы можете сделать:

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

эта функция является ядром библиотека:

function get_realpath() {

if [[ -f "" ]]
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

}

Он также содержит функции get_dirname, get_filename, get_ stemname и validate_path. Попробуйте его на разных платформах и помогите его улучшить.


основываясь на ответе @Andre, у меня может быть немного лучшая версия, если кто-то после решения без цикла, полностью основанного на манипуляции строками. Это также полезно для тех, кто не хочет, чтобы разыменовать какие-либо ссылки, что является недостатком использования realpath или readlink -f.

он работает на версиях bash 3.2.25 и выше.

shopt -s extglob

normalise_path() {
    local path=""
    # 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

проблема с realpath это то, что он недоступен на BSD (или OSX, если на то пошло). Вот простой рецепт, извлеченный из довольно старая (2009) статья из Linux Journal, это довольно портативный:

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

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

обратите внимание, что этот вариант также делает не требуется, чтобы путь существовал.


основываясь на отличном фрагменте python loveborg, я написал следующее:

#!/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

FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

это работает, даже если файл не существует. Для этого требуется каталог, содержащий файл.


мне нужно было решение, которое сделало бы все три:

  • работа на складе Mac. realpath и readlink -f есть аддоны
  • разрешать симлинки
  • есть обработка ошибок

ни один из ответов как #1 и #2. Я добавил #3, чтобы спасти других от дальнейшего бритья яка.

#!/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")"

вот короткий тестовый случай с некоторыми скрученными пробелами в путях, чтобы полностью выполнить цитирование

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 "

Я знаю, что это древний вопрос. Я все еще предлагаю альтернативу. Недавно я столкнулся с той же проблемой и не нашел существующей и переносимой команды для этого. Поэтому я написал следующий скрипт, который содержит функцию, которая может сделать трюк.

#! /bin/sh                                                                                                                                                

function normalize {
  local rc=0
  local ret

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

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

    ret=`echo  | 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


я обнаружил сегодня, что вы можете использовать stat команда для разрешения путей.

Итак, для каталога типа "~ / Documents":

вы можете выполнить следующее:

stat -f %N ~/Documents

чтобы получить полный путь:

/Users/me/Documents

для символических ссылок можно использовать параметр % y format:

stat -f %Y example_symlink

который может вернуть результат, например:

/usr/local/sbin/example_symlink

параметры форматирования могут отличаться на другие версии *NIX, но они работали для меня на OSX.


простое решение с помощью node.js:

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