Как вы нормализуете путь к файлу в Bash? - PullRequest
174 голосов
/ 12 ноября 2008

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

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


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

Ответы [ 21 ]

167 голосов
/ 12 ноября 2008

, если вы хотите скомпоновать часть имени файла из пути, «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/../../

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

91 голосов
/ 12 ноября 2008

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

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

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

53 голосов
/ 12 ноября 2008

Попробуйте 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);
}
35 голосов
/ 30 июля 2010

Переносимым и надежным решением является использование 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 может быть относительным или абсолютным путем.

34 голосов
/ 27 июля 2010

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

MY_PATH=$(readlink -f "$0")
13 голосов
/ 03 октября 2013

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.

9 голосов
/ 14 марта 2016

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

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

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

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

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

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

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

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

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

7 голосов
/ 10 октября 2011

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

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

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

7 голосов
/ 18 июня 2011

Как отметил Адам Лисс, 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.

5 голосов
/ 30 мая 2012

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

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

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

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

Сценарий 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...