it-swarm.com.ru

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

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

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


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

164
Fabien

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

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

161
Kent Fredric

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

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

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

86
Tim Whitcomb

Попробуйте 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);
}
53
Adam Liss

Портативное и надежное решение - использовать 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
loevborg

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

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

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

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

abspath=$(readlink -f $path)

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

abspath=$(readlink -e $path)

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

abspath=$(readlink -m $path)

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

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

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

13
Craig

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

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

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

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

 abspath = "$ (cd -P" $ path "&& pwd -P)" # физический путь с разрешенными символическими ссылками 
 abspath = "$ (cd -L" $ path "&& pwd -L)" # логический путь, сохраняющий символические ссылки 

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

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

name0 = "$ (базовое имя" $ 0 ")"; # базовое имя скрипта 
 dir0 = "$ (cd" $ (dirname "$ 0") "&& pwd)"; #absolute начальный каталог

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

7
Gilbert

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

7
Jeet

Мое недавнее решение было:

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

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

7
schmunk

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

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
5
Jesse Glick

Разговорчивый и немного поздний ответ. Мне нужно написать один, так как я застрял на более старом 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`
4
alhernau

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

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

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

4
apottere

Попробуйте наш новый продукт библиотеки 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. Попробуйте на разных платформах и помогите улучшить его.

3
AsymLabs

Основываясь на ответе @ 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
2
ϹοδεMεδιϲ

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

Обратите внимание, что для этого варианта not требуется путь к существованию.

1
André Anjos

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

  • Работа на стоковом 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 "
0
David Blevins

Основываясь на отличном фрагменте Python от Loveborg, я написал это:

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

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

0
user240515

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

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