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

136

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

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


Изменить: в моем практическом случае каталог существует.

  • 4
    Имеет ли значение, если /foo/bar или даже /foo действительно существуют, или вас интересует только аспект манипуляции со строками в соответствии с правилами имен путей?
  • 5
    @ twalberg ... это как-то придумано ...
Показать ещё 2 комментария
Теги:

18 ответов

142
Лучший ответ

если вы хотите chomp часть имени файла из пути, "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, похоже, не является стандартной проблемой.

Ближе всего вы можете ознакомиться со стандартом на складе:

readlink -f  /path/here/.. 

Realpath, похоже, исходит от debian и не является частью coreutils:  http://packages.debian.org/unstable/utils/realpath Который изначально был частью пакета DWWW.

(также доступен в gentoo как app-admin/realpath)

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

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

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

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

  • 4
    Для тех из вас, кому нужно решение OS X, ознакомьтесь с ответом Адама Лисса ниже.
  • 0
    stackoverflow.com/a/17744637/999943 Это сильно связанный ответ! Я наткнулся на обе эти записи о качестве за один день и хотел связать их вместе.
Показать ещё 2 комментария
79

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

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

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

  • 9
    Это нормализует, но не разрешает мягкие ссылки. Это может быть ошибка или особенность. :-)
  • 1
    @ Adam увидеть человека realpath: realpath -s делает то же самое :)
Показать ещё 7 комментариев
49

Попробуйте 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);
}
  • 1
    Это входит в стандартную установку bash? Я получаю "команда не найдена" в нашей системе (bash 3.00.15 (1) на RHEL)
  • 4
    Я не видел эту команду ни в одной системе, с которой работал.
Показать ещё 14 комментариев
36

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

MY_PATH=$(readlink -f "$0")
  • 0
    У BSD нет флага -f, что означает, что это не удастся даже на последней MacOS Mojave и многих других системах. Забудьте об использовании -f, если вам нужна переносимость, это влияет на множество ОС.
  • 1
    @sorin. Вопрос не в Mac, а в Linux.
27

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

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

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

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

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

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

  • 6
    Спасибо, это был единственный, который работал. readlink или realpath недоступны в OS X. Python должен быть на большинстве платформ.
  • 1
    Просто чтобы уточнить, readlink на readlink доступна в OS X, но не с опцией -f . Портативные обходные пути обсуждаются здесь .
Показать ещё 1 комментарий
12

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

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

abspath=$(readlink -f $path)

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

abspath=$(readlink -e $path)

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

abspath=$(readlink -m $path)

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

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

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

  • 4
    readlink - хороший вариант, если он доступен. Версия OS X не поддерживает опции -e или -f . В первых трех примерах вы должны иметь двойные кавычки вокруг $path для обработки пробелов или подстановочных знаков в имени файла. +1 для расширения параметра, но это имеет уязвимость безопасности. Если path пуст, это будет cd к вашей домашней директории. Вам нужны двойные кавычки. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
  • 0
    Это был просто пример. Если вы одержимы безопасностью, то вам вообще не следует использовать bash или любой другой вариант оболочки. Кроме того, у bash есть свои проблемы, когда речь идет о кроссплатформенной совместимости, а также проблемы с изменением функциональности между основными версиями. OSX - это лишь одна из многих платформ с проблемами, относящимися к сценариям оболочки, не говоря уже о том, что она основана на BSD. Когда вам нужно быть действительно мультиплатформенным, вы должны быть POSIX-совместимым, поэтому расширение параметров действительно выходит за рамки. Посмотрите на Solaris или HP-UX некоторое время.
Показать ещё 1 комментарий
7

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

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

Основываясь на ответе Тима Уиткомба.

  • 0
    Я подозреваю, что это не удастся, если аргумент не является каталогом. Предположим, я хочу знать, куда ведет / usr / bin / java?
  • 0
    Если вы знаете, что это файл, вы можете попробовать pushd $(dirname /usr/bin/java) .
5

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

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

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

  • 0
    Одна ошибка с опцией pwd -P для этого случая ... Рассмотрим, что произойдет, если $(basename "$1") будет символической ссылкой на файл в другом каталоге. pwd -P разрешает только символические ссылки в части пути каталога, но не в части базового имени.
4

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

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

Поскольку cd происходит в подоболочке, это не влияет на основной script.

Две вариации, предполагающие, что ваши встроенные команды оболочки принимают -L и -P,:

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

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

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

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

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

4

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

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

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

4

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

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

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

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

A sed script показалось бы хорошей ставкой, за исключением того, что вы не можете итеративно заменять последовательности (/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
  • 0
    realpath есть опция -s чтобы не разрешать символические ссылки, а разрешать только ссылки на /./ , /../ и удалять лишние символы / . В сочетании с параметром -m realpath работает только с именем файла и не затрагивает какой-либо фактический файл. Это звучит как идеальное решение. Но, увы, realpath все еще отсутствует во многих системах.
  • 0
    Удаление .. компонентов не может быть сделано синтаксически, когда задействованы символические ссылки. /one/two/../three - это не то же самое, что /one/three если two - символическая ссылка на /foo/bar .
Показать ещё 3 комментария
4

Говорит, и немного поздно ответ. Мне нужно написать его, так как я застрял на старших RHEL4/5. Я обрабатываю абсолютные и относительные ссылки и упрощает//,/./и 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`
3

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

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

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

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

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

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

}

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

1

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

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

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
  • 0
    Это хорошая идея, но я потратил 20 минут, пытаясь заставить это работать на разных версиях bash. Оказывается, для работы этой опции должна быть включена опция оболочки extglob, и это не по умолчанию. Когда речь заходит о функциональности bash, важно указать как необходимую версию, так и параметры, отличные от заданных по умолчанию, поскольку эти данные могут различаться в разных ОС. Например, последняя версия Mac OSX (Yosemite) поставляется только с устаревшей версией bash (3.2).
  • 0
    Извините @ricovox; Я сейчас обновил их. Мне не терпится узнать точную версию Bash, которая у вас есть. Приведенная выше формула (обновленная) работает на CentOS 5.8, которая поставляется с bash 3.2.25
Показать ещё 2 комментария
1

Проблема с 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
}

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

0

Основанный на отличном фрагменте 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
0

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

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

Вы можете запустить это:

stat -f %N ~/Documents

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

/Users/me/Documents

Для символических ссылок вы можете использовать опцию формата% Y:

stat -f %Y example_symlink

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

/usr/local/sbin/example_symlink

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

  • 1
    строка stat -f %N ~/Documents представляет собой красную сельдь ... ваша оболочка заменяет ~/Documents на /Users/me/Documents , а stat просто печатает ее аргумент дословно.
-4

Простое решение с использованием node.js:

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню