Як отримати абсолютний шлях заданого відносного


159

Чи є команда для отримання абсолютного шляху, заданого відносним шляхом?

Наприклад, я хочу, щоб $ line містив абсолютний шлях кожного файлу в dir ./etc/

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


1
не строгий дублікат, але схожий
mpapis


Набагато кращим рішенням, ніж будь-який із перерахованих до цих пір, є ось, як перетворити відносний шлях-в-абсолют-шлях-в-unix
Даніель Генін

Відповіді:


66

використання:

find "$(pwd)"/ -type f

щоб отримати всі файли або

echo "$(pwd)/$line"

відображення повного шляху (якщо відносний шлях має значення)


2
Що робити, якщо я знаходжу відносний шлях у знаходженні ??
nubme

17
потім перегляньте посилання в коментарі до вашого питання, найкращий спосіб, ймовірно, - це "readlink -f $ line"
mpapis

3
Навіщо використовувати $(pwd)замість $PWD? (так, я знаю pwd, що це вбудований)
Адам Кац

178

Спробуйте 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вже присутній. Мені не довелося встановлювати його окремо.
Акумен

Дивно, але realpathвін доступний на Git для Windows (принаймні для мене).
Ендрю Кітон

104

Якщо у вас встановлений пакет coreutils, ви можете, як правило, використовувати readlink -f relative_file_nameдля отримання абсолютного (з усіма посиланнями, що вирішені)


3
Поведінка для цього трохи відрізняється від того, що запитує користувач, воно також буде слідувати і вирішувати рекурсивні посилання в будь-якій точці шляху. Можливо, ви не хочете цього в деяких випадках.
ffledgling

1
@BradPeabody Це працює в Mac, якщо ви встановлюєте coreutils з домашньої програми brew install coreutils. Однак виконуваний файл попередньо попереджує: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 або файл до цього сценарію):dirname "$1"
  3. Тоді ми cd "$(dirname "$1")переходимо до цього відносного режиму і отримуємо абсолютний шлях до нього, запустивши pwdкоманду shell
  4. Після цього додаємо базове ім'я до абсолютного шляху:$(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
Мені подобається відповідь, але вона працює лише в тому випадку, якщо користувачеві дозволено вводити CD в каталог. Це не завжди можливо.
Маттіас Б

34

Що варто, я проголосував за обрану відповідь, але хотів поділитися рішенням. Мінус у тому, що це лише Linux - я витратив близько 5 хвилин, намагаючись знайти еквівалент OSX, перш ніж прийти до переповнення Stack. Я впевнений, що все-таки там.

У 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. Це допомогло мені отримати абсолютний шлях символічного зв’язку - не його цілі.
kevinarpe

Не працює, коли ви хочете повернути шлях до символічних посилань (що мені просто потрібно зробити ...).
Томаш Зато - Відновити Моніку

Як ви могли коли-небудь знайти абсолютний шлях до неіснуючого шляху?
синтезаторпатель

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

25

Я думаю, що це самий портативний:

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

Він не зможе, якщо шлях не існує.


3
Немає необхідності знову видавати компакт-диск. Див. Stackoverflow.com/a/21188136/1504556 . Ваша відповідь - найкраща відповідь на цій сторінці, ІМХО. Для тих, хто цікавиться, посилання дає пояснення, чому це рішення працює.
peterh

Це не дуже портативно, dirnameє основною утилітою GNU, не загальною для всіх unixen, я вважаю.
einpoklum

@einpoklum dirname- стандартна утиліта POSIX, дивіться тут: pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html
Ernest A

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

Зверніть увагу: це не робить все правильно, якщо шлях закінчується../..
Алекс Ковентрі

16

realpath це, мабуть, найкраще

Але ...

Початкове запитання було дуже заплутано для початку, на прикладі погано пов'язаного із зазначеним питанням.

Вибрана відповідь насправді відповідає наведеному прикладу, а зовсім не на запитання в заголовку. Перша команда - це відповідь (це насправді? Я сумніваюся), і могла б обійтися і без '/'. І я не бачу, що робить друга команда.

Кілька питань змішані:

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

  • може бути кілька абсолютних імен траєкторій, які позначають один і той же файл (або потенційний файл), особливо через символічні посилання (символьні посилання) на шляху, але, можливо, з інших причин (пристрій може бути встановлено вдвічі, ніж лише для читання). Можна або не хочеться вирішувати чіткість таких посилань.

  • дістатися до кінця ланцюжка символічних посилань на файл або ім'я, яке не є символьним посиланням. Це може або не може дати абсолютну назву шляху, залежно від того, як це робиться. І можна, а може і не захоче вирішити це в абсолютне ім'я шляху.

Команда readlink fooбез параметра дає відповідь лише у тому випадку, якщо її аргумент fooє символічним посиланням, і ця відповідь є значенням цього посилання. Жодного іншого посилання не виконується. Відповідь може бути відносним шляхом: яким би не було значення аргументу symlink.

Однак readlinkє параметри (-f -e або -m), які працюватимуть для всіх файлів, і дають одне абсолютне ім'я шляху (те, що не має посилань) на файл, фактично позначений аргументом.

Це добре працює для будь-якого, що не є символьним посиланням, хоча, можливо, хочеться використовувати абсолютне ім'я шляху, не вирішуючи проміжні посилання на шляху. Це робиться командоюrealpath -s foo

У випадку аргументу symlink, 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\")"

Бічна примітка, ваш поточний робочий каталог не впливає.


Зверніть увагу: це сценарій. де 1 долар - це перший аргумент
Євген Конков

5

Найкраще рішення, імхо, - це таке, розміщене тут: https://stackoverflow.com/a/3373298/9724628 .

Це вимагає роботи пітона, але, схоже, охоплює всі або більшість крайових випадків і є дуже портативним рішенням.

  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

Якщо ви використовуєте bash на Mac OS X, який не існував ні realpath, ні його посилання для читання не може надрукувати абсолютний шлях, у вас може бути вибір, крім кодування власної версії для його друку. Ось моя реалізація:

(чистий баш)

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або (в баш) 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
Michael

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

3
Дякуємо за цей фрагмент коду, який може надати деяку короткочасну допомогу. Правильне пояснення значно покращило б його довгострокове значення, показавши, чому це хороше рішення проблеми, та зробило б кориснішим майбутнім читачам інші подібні питання. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення, включаючи зроблені вами припущення.
Toby Speight

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")"у @ ernest-a отримає як accurate_sub_path/spurious_subdirectory/...


0

Якщо ви хочете перетворити змінну, що містить відносний шлях, в абсолютну, це працює:

   dir=`cd "$dir"`

"cd" відлунюється, не змінюючи робочий каталог, тому що виконується тут у підколонці.


2
На bash-4.3-p46 це не працює: оболонка друкує порожній рядок під час запускуdir=`cd ".."` && echo $dir
Майкл

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 / substitu },
яка тут добре обговорюється: 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.