Как получить абсолютный путь заданного относительного


160

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

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

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


1
не прямо дубликат, но похоже
mpapis


Гораздо лучшее решение, чем любое из перечисленных выше, заключается в том, как конвертировать относительный путь в абсолютный путь в unix
Даниэль Генин

Ответы:


67

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

find "$(pwd)"/ -type f

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

echo "$(pwd)/$line"

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


2
что если я укажу относительный путь в поиске ??
2010 года

17
затем просмотрите ссылки в комментариях к вашему вопросу, лучший способ, вероятно, это «readlink -f $ line»
mpapis

3
Зачем использовать $(pwd)вместо $PWD? (да, я знаю pwd, это встроенный)
Адам Кац

179

Попробуй realpath.

~ $ sudo apt-get install realpath  # may already be installed
~ $ realpath .bashrc
/home/username/.bashrc

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

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


11
realpathкажется, не доступно на Mac (OS X 10.11 "El Capitan"). :-(
Ларикс Децидуа

realpathПохоже, что он недоступен в CentOS 6
user5359531

6
на OSX, brew install coreutilsпринесетrealpath
Кевин Чен

На моем Ubuntu 18.04, realpathуже присутствует. Мне не нужно было устанавливать его отдельно.
Acumenus

Удивительно, но realpathдоступен на Git для Windows (для меня, по крайней мере).
Эндрю Китон

104

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


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

1
@BradPeabody Это работает на Mac, если вы устанавливаете coreutils из homebrew brew install coreutils. Однако к исполняемому файлу добавляется ag:greadlink -f relative_file_name
Мигель Исла

2
Обратите внимание, что страница руководства readlink (1) имеет в качестве первого предложения своего описания: «Обратите внимание, что realpath (1) является предпочтительной командой для использования для функций канонизации».
Йош

1
вы можете использовать -e вместо -f, чтобы проверить, существует ли файл / каталог или нет
Iceman

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

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

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

8
В этом ответе используется лучшая идиома Баш
Рондо

2
readlink - это простое решение для linux, но это решение работает и на OSX, поэтому +1
thetoolman

1
Этот сценарий не эквивалентен тому, что realpathили readlink -fделать. Например, он не работает с путями, где последний компонент является символической ссылкой.
Йош

2
@josch: Вопрос не в разрешении символических ссылок. Но если вы хотите сделать это, вы можете предоставить -Pопцию pwdкоманды:echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"
Евгений Коньков

4
Мне нравится ответ, но он работает только в том случае, если пользователю разрешено перейти в каталог. Это не всегда возможно.
Матиас Б

34

За то, что это стоит, я проголосовал за ответ, который был выбран, но хотел поделиться решением. Недостатком является то, что это только 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ничего не вернете, и в конечном итоге вы получите двойную косую черту.


Отличная запись с dirname, readlinkи basename. Это помогло мне получить абсолютный путь символической ссылки, а не ее цель.
Кевинарпе

Не работает, когда вы хотите вернуть путь к символическим ссылкам (что мне просто нужно сделать ...).
Томаш Зато - Восстановить Монику

Как вы можете найти абсолютный путь к пути, который не существует?
синтезаторпатель

@synthesizerpatel Довольно легко, я бы подумал; если я вхожу /home/GKFXи touch newfileпечатаю, то перед нажатием клавиши ввода вы можете решить, что я имею в виду «create / home / GKFX / newfile», то есть абсолютный путь к файлу, который еще не существует.
GKFX

25

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

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

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


3
Там нет необходимости снова CD. См. Stackoverflow.com/a/21188136/1504556 . Ваш лучший ответ на этой странице, ИМХО. Для интересующихся ссылка дает объяснение, почему это решение работает.
Петер

Это не очень переносимая dirnameутилита ядра GNU, которая, на мой взгляд, не является общей для всех Unixen.
einpoklum

@einpoklum dirname- стандартная утилита POSIX, см. здесь: pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html
Эрнест А,

Боже мой, спасибо. Я пытался исправить версию, которая используется ${1##*/}в течение дня, и теперь, когда я заменил этот мусор, basename "$1"он, кажется, наконец-то правильно обрабатывает пути, заканчивающиеся на /.
l3l_aze

NB, это не делает правильную вещь с путем, заканчивающимся в../..
Алекс Ковентри

16

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это лучшая команда для использования с максимальной гибкостью, по крайней мере, для использования, запрошенного здесь.


10

Мое любимое решение было один на @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
}

7

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

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

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


Обратите внимание: это сценарий. где первый аргумент
Евгений Коньков

5

Наилучшим решением, imho, является то, которое размещено здесь: https://stackoverflow.com/a/3373298/9724628 .

Для работы требуется 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

3

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

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

3

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

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

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

1

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


1

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

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

1

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

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

echo $absPath

Это решение работает только для bash, см. Также stackoverflow.com/a/5193087/712014
Майкл

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

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

1

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

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

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


0

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

   dir=`cd "$dir"`

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


2
На bash-4.3-p46 это не работает: оболочка печатает пустую строку, когда я запускаюdir=`cd ".."` && echo $dir
Michael

0

Это связанное решение от всех остальных, например, когда происходит 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")" "${?}"

0

Вы можете использовать подстановку строки 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.

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


0

Мне не удалось найти решение, которое было бы легко переносимым между 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"
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.