Bash: получить абсолютный путь, заданный относительным

Есть ли команда для получения абсолютного пути по заданному пути?

Например, я хочу, чтобы $line содержал абсолютный путь к каждому файлу в директории ./etc/

find ./ -type f | while read line; do
   echo $line
done

26 ответов

Решение

Использовать:

find $(pwd)/ -type f

чтобы получить все файлы или

echo $(pwd)/$line

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

Пытаться realpath,

~ $ sudo apt-get install realpath
~ $ realpath .bashrc
/home/username/.bashrc

Ответ приходит от команды bash/fish для вывода абсолютного пути к файлу.

Если у вас установлен пакет coreutils, вы можете использовать readlink -f relative_file_name для получения абсолютного (со всеми разрешенными символическими ссылками)

#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

UPD Некоторые объяснения

  1. Этот скрипт получает относительный путь в качестве аргумента "$1"
  2. Затем мы получаем часть dirname этого пути (вы можете передать либо dir, либо файл в этот скрипт): dirname "$1"
  3. Тогда мы cd "$(dirname "$1") в этот относительный каталог и получить абсолютный путь для него, запустив pwd команда оболочки
  4. После этого мы добавляем basename к абсолютному пути: $(basename "$1")
  5. В качестве последнего шага мы echo Это

За то, что это стоит, я проголосовал за ответ, который был выбран, но хотел поделиться решением. Недостатком является то, что это только Linux - я потратил около 5 минут, пытаясь найти эквивалент OSX, прежде чем перейти к переполнению стека. Я уверен, что это там, хотя

В Linux вы можете использовать readlink -e В паре с dirname,

$(dirname $(readlink -e ../../../../etc/passwd))

доходность

/etc/

И тогда вы используете dirnameсестра, basename просто получить имя файла

$(basename ../../../../../passwd)

доходность

passwd

Положил все это вместе..

F=../../../../../etc/passwd
echo "$(dirname $(readlink -e $F))/$(basename $F)"

доходность

/etc/passwd

Вы в безопасности, если вы нацелены на каталог, basename ничего не вернет, и в конечном итоге вы получите двойную косую черту.

Я думаю, что это самый портативный:

abspath() {                                               
    cd "$(dirname "$1")"
    printf "%s/%s\n" "$(pwd)" "$(basename "$1")"
    cd "$OLDPWD"
}

Это не удастся, если путь не существует, хотя.

realpath наверное лучше

Но...

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

Выбранный ответ на самом деле отвечает приведенному примеру, а не на вопрос в заголовке. Первая команда - это ответ (действительно ли это? Я сомневаюсь), и она может обойтись без '/'. И я не вижу, что делает вторая команда.

Несколько вопросов смешаны:

  • изменение относительного пути в абсолютное, что бы оно ни обозначало, возможно, ничего. (Как правило, если вы выполните команду, такую ​​как touch foo/bar , путь foo/bar должен существовать для вас и, возможно, использоваться в вычислениях до того, как файл будет фактически создан.)

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

  • добраться до конца цепочки символических ссылок на несимвольный файл или имя. Это может или не может привести к абсолютному пути, в зависимости от того, как это делается. И кто-то может или не может хотеть разрешить его в абсолютном пути.

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

Тем не мение, readlink имеет параметры (-f -e или -m), которые будут работать для всех файлов и давать одно абсолютное имя пути (без символьных ссылок) для файла, фактически обозначенного аргументом.

Это прекрасно работает для всего, что не является символической ссылкой, хотя можно использовать абсолютный путь без разрешения промежуточных символических ссылок в пути. Это делается командой realpath -s foo

В случае аргумента символической ссылки, readlink с его параметрами снова разрешит все символические ссылки на абсолютном пути к аргументу, но это также будет включать все символические ссылки, которые могут встретиться после следования значения аргумента. Возможно, вы не захотите этого, если вам нужен абсолютный путь к самой символической ссылке на аргумент, а не к тому, на что он может ссылаться. Опять же, если foo символическая ссылка, realpath -s foo получит абсолютный путь без разрешения символических ссылок, включая указанную в качестве аргумента.

Без -s вариант, realpath делает почти так же, как readlink За исключением простого чтения значения ссылки, а также нескольких других вещей. Мне просто непонятно почему readlink имеет свои варианты, создавая, по-видимому, нежелательную избыточность с realpath,

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

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

Моим любимым решением было решение @EugenKonkov, поскольку оно не подразумевало наличия других утилит (пакет coreutils).

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

Это все еще терпит неудачу, если пользователь не имеет разрешения на cd в родительский каталог относительного пути, хотя.

#! /bin/sh

# Takes a path argument and returns it as an absolute path. 
# No-op if the path is already absolute.
function to-abs-path {
    local target="$1"

    if [ "$target" == "." ]; then
        echo "$(pwd)"
    elif [ "$target" == ".." ]; then
        echo "$(dirname "$(pwd)")"
    else
        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
    fi
}

Ответ Евгения не совсем сработал для меня, но это сработало:

    absolute="$(cd $(dirname \"$file\"); pwd)/$(basename \"$file\")"

Примечание: ваш текущий рабочий каталог не затронут.

Наилучшим решением, imho, является то, которое размещено здесь: /questions/11021087/kak-vyi-normalizuete-put-k-fajlu-v-bash/11021088#11021088.

Для работы требуется python, но, похоже, он охватывает все или большинство крайних случаев и является очень портативным решением.

  1. С разрешением символических ссылок:
python -c "import os,sys; print os.path.realpath(sys.argv[1])" path/to/file
  1. или без него:
python -c "import os,sys; print os.path.abspath(sys.argv[1])" path/to/file

Улучшение довольно красивой версии @ernest-a:

absolute_path() {
    cd "$(dirname "$1")"
    case $(basename $1) in
        ..) echo "$(dirname $(pwd))";;
        .)  echo "$(pwd)";;
        *)  echo "$(pwd)/$(basename $1)";;
    esac
}

Это правильно для случая, когда последний элемент пути .., в этом случае "$(pwd)/$(basename "$1")" в ответе @ernest-a будет отображаться как accurate_sub_path/spurious_subdirectory/...

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

find /etc
find `pwd`/subdir_of_current_dir/ -type f

Если вы используете bash в Mac OS X, у которой нет ни реального пути, ни ссылки на чтение, вы можете напечатать абсолютный путь, у вас может быть выбор, кроме как написать собственную версию, чтобы распечатать его. Вот моя реализация:

(чистый удар)

abspath(){
  local thePath
  if [[ ! "$1" =~ ^/ ]];then
    thePath="$PWD/$1"
  else
    thePath="$1"
  fi
  echo "$thePath"|(
  IFS=/
  read -a parr
  declare -a outp
  for i in "${parr[@]}";do
    case "$i" in
    ''|.) continue ;;
    ..)
      len=${#outp[@]}
      if ((len==0));then
        continue
      else
        unset outp[$((len-1))] 
      fi
      ;;
    *)
      len=${#outp[@]}
      outp[$len]="$i"
      ;;
    esac
  done
  echo /"${outp[*]}"
)
}

(используйте gawk)

abspath_gawk() {
    if [[ -n "$1" ]];then
        echo $1|gawk '{
            if(substr($0,1,1) != "/"){
                path = ENVIRON["PWD"]"/"$0
            } else path = $0
            split(path, a, "/")
            n = asorti(a, b,"@ind_num_asc")
            for(i in a){
                if(a[i]=="" || a[i]=="."){
                    delete a[i]
                }
            }
            n = asorti(a, b, "@ind_num_asc")
            m = 0
            while(m!=n){
                m = n
                for(i=1;i<=n;i++){
                    if(a[b[i]]==".."){
                        if(b[i-1] in a){
                            delete a[b[i-1]]
                            delete a[b[i]]
                            n = asorti(a, b, "@ind_num_asc")
                            break
                        } else exit 1
                    }
                }
            }
            n = asorti(a, b, "@ind_num_asc")
            if(n==0){
                printf "/"
            } else {
                for(i=1;i<=n;i++){
                    printf "/"a[b[i]]
                }
            }
        }'
    fi
}

(чистый BSD AWK)

#!/usr/bin/env awk -f
function abspath(path,    i,j,n,a,b,back,out){
  if(substr(path,1,1) != "/"){
    path = ENVIRON["PWD"]"/"path
  }
  split(path, a, "/")
  n = length(a)
  for(i=1;i<=n;i++){
    if(a[i]==""||a[i]=="."){
      continue
    }
    a[++j]=a[i]
  }
  for(i=j+1;i<=n;i++){
    delete a[i]
  }
  j=0
  for(i=length(a);i>=1;i--){
    if(back==0){
      if(a[i]==".."){
        back++
        continue
      } else {
        b[++j]=a[i]
      }
    } else {
      if(a[i]==".."){
        back++
        continue
      } else {
        back--
        continue
      }
    }
  }
  if(length(b)==0){
    return "/"
  } else {
    for(i=length(b);i>=1;i--){
      out=out"/"b[i]
    }
    return out
  }
}

BEGIN{
  if(ARGC>1){
    for(k=1;k<ARGC;k++){
      print abspath(ARGV[k])
    }
    exit
  }
}
{
  print abspath($0)
}

пример:

$ abspath I/am/.//..//the/./god/../of///.././war
/Users/leon/I/the/war

Я нашел ответ Евгения Конькова лучшим, поскольку он не требует установки какой-либо программы. Однако это не удастся для несуществующих каталогов.

Я написал функцию, которая работает для несуществующих каталогов:

      function getRealPath()
{
    local -i traversals=0
    currentDir="$1"
    basename=''
    while :; do
        [[ "$currentDir" == '.' ]] && { echo "$1"; return 1; }
        [[ $traversals -eq 0 ]] && pwd=$(cd "$currentDir" 2>&1 && pwd) && { echo "$pwd/$basename"; return 0; }
        currentBasename="$(basename "$currentDir")"
        currentDir="$(dirname "$currentDir")"
        [[ "$currentBasename" == '..' ]] && (( ++traversals )) || { [[ traversals -gt 0 ]] && (( traversals-- )) || basename="$currentBasename/$basename"; }
    done
}

Он решает проблему несуществующих каталогов, переходя до cd успешно, затем возвращается текущий каталог плюс все, что было удалено dirname.

БЛЕФ:

      cd $relative_path ; pwd

Вот объяснение, совместимое с POSIX (я думаю), поэтому оно должно работать на любой платформе.

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

Вы можете использовать which, locate, find, полные пути, что угодно.

x=имя_вашего_файла

      $ x="nvi" 

fileлегко указывает символические ссылки

      $ file -h `which $x`
/usr/local/bin/nvi: symbolic link to ../Cellar/nvi/1.81.6_5/bin/nvi

Затем немного подкорректируйте вывод, чтобы получить «полный» относительный путь.
Нам просто нужно удалить среднюю часть в этом примере.

Обратите внимание, что ЗАГЛАВНАЯ буква Y против строчной буквы х. Вероятно, есть более чистый способ сделать это.

      $ Y=$(file -h `which $x` | sed "s/$x: symbolic link to //")


$ echo $Y
/usr/local/bin/../Cellar/nvi/1.81.6_5/bin/nvi

С использованием dirnameмы получаем только часть пути. cdк этому и имя должно очиститься.

      $ cd `dirname $Y` ; pwd
/usr/local/Cellar/nvi/1.81.6_5/bin

Это приводит нас к старому трюку UNIX: «призрачная прогулка» по каталогу, делая все это в круглых скобках / вложенной оболочке. Это эффективно возвращает нас в наш текущий каталог, когда закончите.

Мы можем вставить фактическое имя файла обратно в конец для полноты картины.

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

      $ ( cd `dirname ${Y}` ; ls `pwd`/${x} )
/usr/local/Cellar/nvi/1.81.6_5/bin/nvi

Так /usr/local/bin/nviдействительно /usr/local/Cellar/nvi/1.81.6_5/bin/nvi

Упрощенный пример быстрого «преобразования» пути:

      $ (cd /usr/local/bin/../Cellar/nvi/1.81.6_5/bin ; pwd)
/usr/local/Cellar/nvi/1.81.6_5/bin

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/file.htmlhttps://pubs.opengroup.org/onlinepubs/9699919799/utilities/имя_каталога.html

Похоже на ответ @ernest-a, но не влияет $OLDPWD или определить новую функцию, которую вы могли бы запустить подоболочку (cd <path>; pwd)

$ pwd
/etc/apache2
$ cd ../cups 
$ cd -
/etc/apache2
$ (cd ~/..; pwd)
/Users
$ cd -
/etc/cups

Мне не удалось найти решение, которое было бы аккуратно переносимым между Mac OS Catalina, Ubuntu 16 и Centos 7, поэтому я решил сделать это с помощью встроенного Python, и оно хорошо работало для моих сценариев bash.

to_abs_path() {
  python -c "import os; print os.path.abspath('$1')"
}

to_abs_path "/some_path/../secrets"

Вы можете использовать замену строки bash для любого относительного пути $line:

line=$(echo ${line/#..\//`cd ..; pwd`\/})
line=$(echo ${line/#.\//`pwd`\/})
echo $line

Базовая подстановка в начале строки следует формуле
${string/#substring/replace},
которая хорошо обсуждается здесь: https://www.tldp.org/LDP/abs/html/string-manipulation.html

В \ характер отрицает / когда мы хотим, чтобы он был частью строки, которую мы находим / заменяем.

Что они сказали, кроме find $PWD или (в bash) find ~+ немного удобнее.

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

absPath=$(pushd ../SOME_RELATIVE_PATH_TO_Directory > /dev/null && pwd && popd > /dev/null)

echo $absPath
echo "mydir/doc/ mydir/usoe ./mydir/usm" |  awk '{ split($0,array," "); for(i in array){ system("cd "array[i]" && echo $PWD") } }'

Вот довольно короткая функция, которую можно использовать для полной абсолютизации и канонизации любого заданного пути ввода, используя только оболочку POSIX и readlink семантика:

canonicalize_path() {
    (
        FILEPATH="$1"
        for _ in 1 2 3 4 5 6 7 8;  # Maximum symlink recursion depth
        do
            cd -L "`case "${FILEPATH}" in */*) echo "${FILEPATH%/*}";; *) echo ".";; esac`/"  # cd $(dirname)
            if ! FILEPATH="$(readlink "${FILEPATH##*/}" || ( echo "${FILEPATH##*/}" && false ) )";
            then
                break
            fi
        done
        cd -P "." || return $?
        echo "$(pwd)/${FILEPATH}"
    )
}

Если указанный файл не существует, будет разрешен только путь к каталогу, ведущий к окончательному имени файла. Если какой-либо из каталогов, ведущих к пути к файлу, не существует, cdошибка будет возвращена. Это точная семантика GNU/Linux. readlink -f команду он пытается имитировать.

В bash/zsh вы также можете сжать список чисел, чтобы просто {1..8}или похожие. Было выбрано число 8, поскольку это был максимальный предел в Linux в течение многих лет, прежде чем его изменили на общий предел разрешения 40 для всего пути в версии 4.2. Если предел разрешения достигнут, код не завершится ошибкой, а вместо этого вернет последний рассматриваемый путь - явный [ -L "${FILEPATH}" ] Однако можно добавить проверку для обнаружения этого условия.

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

FILEPATH="$0"
for _ in 1 2 3 4 5 6 7 8;  # Maximum symlink recursion depth
do
    cd -L "`case "${FILEPATH}" in */*) echo "${FILEPATH%/*}";; *) echo ".";; esac`/"  # cd $(dirname)
    if ! FILEPATH="$(readlink "${FILEPATH##*/}" || ( echo "${FILEPATH##*/}" && false ) )";
    then
        break
    fi
done
cd -P "."
FILEPATH="$(pwd)/${FILEPATH}"

Обновление предыдущего ответа с использованием python 3

      to_abs_path() {
  python -c "import os; print (os.path.abspath('$1'))"
}

Разрешение

      to_abs_path "./../some_file.txt"

Based on this answer by Eugen Konkov and this answer by hashchange, my answer combines the brevity of the former with the handling of . and .. of the latter. I believe that all the options below rely upon nothing more than the basic Shell Command Language POSIX standards.

Using and , an option is:

      absPathDirname()
{
    [ -d "${1}" ] && set -- "${1}" || set -- "`dirname "${1}"`" "/`basename "${1}"`"
    echo "`cd "${1}"; pwd`${2}";
}

Without using dirname or basename, another brief option is:

      absPathMinusD()
{
    [ -d "${1}" ] && set -- "${1}" || set -- "${1%${1##*/}}" "/${1##*/}"
    echo "`cd "${1:-.}"; pwd`${2}";
}

I would recommend one of the two options above, the rest are just for fun...

Grep version:

      absPathGrep()
{
    echo "`[ "${1##/*}" ] && echo "$1" | grep -Eo '^(.*/)?\.\.($|/)' | { read d && cd "$d"; echo "${PWD}/${1#$d}"; } || echo "$1"`"
}

As an interesting example of "what can be done with the shell's limited RegEx":

      absPathShellReplace()
{
    E="${1##*/}"; D="${E#$E${E#.}}"; DD="${D#$D${D#..}}"
    DIR="${1%$E}${E#$DD}"; FILE="${1#$DIR}"; SEP=${FILE:+/}
    echo "`cd "${DIR:-.}"; pwd`${SEP#$DIR}$FILE"
}

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

   dir=`cd "$dir"`

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

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

#!/bin/bash

function getabsolutepath() {
    local target;
    local changedir;
    local basedir;
    local firstattempt;

    target="${1}";
    if [ "$target" == "." ];
    then
        printf "%s" "$(pwd)";

    elif [ "$target" == ".." ];
    then
        printf "%s" "$(dirname "$(pwd)")";

    else
        changedir="$(dirname "${target}")" && basedir="$(basename "${target}")" && firstattempt="$(cd "${changedir}" && pwd)" && printf "%s/%s" "${firstattempt}" "${basedir}" && return 0;
        firstattempt="$(readlink -f "${target}")" && printf "%s" "${firstattempt}" && return 0;
        firstattempt="$(realpath "${target}")" && printf "%s" "${firstattempt}" && return 0;

        # If everything fails... TRHOW PYTHON ON IT!!!
        local fullpath;
        local pythoninterpreter;
        local pythonexecutables;
        local pythonlocations;

        pythoninterpreter="python";
        declare -a pythonlocations=("/usr/bin" "/bin");
        declare -a pythonexecutables=("python" "python2" "python3");

        for path in "${pythonlocations[@]}";
        do
            for executable in "${pythonexecutables[@]}";
            do
                fullpath="${path}/${executable}";

                if [[ -f "${fullpath}" ]];
                then
                    # printf "Found ${fullpath}\\n";
                    pythoninterpreter="${fullpath}";
                    break;
                fi;
            done;

            if [[ "${pythoninterpreter}" != "python" ]];
            then
                # printf "Breaking... ${pythoninterpreter}\\n"
                break;
            fi;
        done;

        firstattempt="$(${pythoninterpreter} -c "import os, sys; print( os.path.abspath( sys.argv[1] ) );" "${target}")" && printf "%s" "${firstattempt}" && return 0;
        # printf "Error: Could not determine the absolute path!\\n";
        return 1;
    fi
}

printf "\\nResults:\\n%s\\nExit: %s\\n" "$(getabsolutepath "./asdfasdf/ asdfasdf")" "${?}"
Другие вопросы по тегам