Розгорніть можливий відносний шлях у bash


82

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

Чи є якась функція для цього?


1
Спробуйте функцію читання посилань - andy.wordpress.com/2008/05/09/bash-equivalent-for-php-realpath
arunkumar

7
readlinkвирішує символічні посилання; ОП цього не хоче.
Кіт Томпсон,



1
echo $(cd some_directory && pwd), не вирішує символічне посилання, не вдається, якщо якийсь_каталог не існує. робочий каталог не впливає. або призначити змінній MY_PATH=$(cd some_directory && pwd).
qeatzy

Відповіді:


79

MY_PATH=$(readlink -f $YOUR_ARG)вирішить відносні шляхи, такі як "./"і"../"

Подумайте і про це ( джерело ):

#!/bin/bash
dir_resolve()
{
cd "$1" 2>/dev/null || return $?  # cd to desired directory; if fail, quell any error messages but return exit status
echo "`pwd -P`" # output full, link-resolved path
}

# sample usage
if abs_path="`dir_resolve \"$1\"`"
then
echo "$1 resolves to $abs_path"
echo pwd: `pwd` # function forks subshell, so working directory outside function is not affected
else
echo "Could not reach $1"
fi

це, звичайно, приємно, але проблема в тому, що симлінки вирішені, і це призведе до певної плутанини з користувачами.
laar

man pwd: "-P Відобразити поточний фізичний робочий каталог (усі символічні посилання вирішено)", просто видаліть-P
andsens

7
Щоб отримати readlink -fфункціональність на OS X, ви можете використовувати Homebrew, а потім запустити: $ brew install coreutils Потім ви можете використовувати greadlink -f.
jgpawletko

46

http://www.linuxquestions.org/questions/programming-9/bash-script-return-full-path-and-filename-680368/page3.html має таке

function abspath {
    if [[ -d "$1" ]]
    then
        pushd "$1" >/dev/null
        pwd
        popd >/dev/null
    elif [[ -e "$1" ]]
    then
        pushd "$(dirname "$1")" >/dev/null
        echo "$(pwd)/$(basename "$1")"
        popd >/dev/null
    else
        echo "$1" does not exist! >&2
        return 127
    fi
}

який використовує pushd/, popdщоб потрапити в стан, де pwdце корисно.


2
На жаль, це дійсно найкраща відповідь тут, і вона не проголосована там, де вона належить. +1 за точну відповідь на запитання ОП. Він хоче, щоб символічні посилання не були вирішені, що насправді є питанням місцевого баша DIRSTACKі нічого іншого. Приємне рішення!
cptstubing06

5
Прагнучи до стандартної shсумісності, pushd; cd <dir>; <cmd>; popdйого можна замінити на (cd <dir>; <cmd>).
Аббафей

16

Простий однокласний:

function abs_path {
  (cd "$(dirname '$1')" &>/dev/null && printf "%s/%s" "$PWD" "${1##*/}")
}

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

function do_something {
    local file=$(abs_path $1)
    printf "Absolute path to %s: %s\n" "$1" "$file"
}
do_something $HOME/path/to/some\ where

Я все ще намагаюся зрозуміти, як я можу змусити його не звертати уваги на те, чи існує шлях чи ні (тому його можна використовувати також при створенні файлів).


1
Це не змінить поточний робочий каталог? Чи слід cd -після цього скидати його?
devios1,

4
Нема потреби. Зверніть увагу на паренси, вони призводять до того, що команди всередині виконуються в підшелутці. Стан (наприклад, робочий каталог) підшорстки не витікає до її батьківської оболонки. Детальніше читайте тут: tldp.org/LDP/abs/html/subshells.html
andsens

1
Хороший однокласник. Однак є проблема: якщо в розширеному значенні $ 1 немає скісної риски (тобто це ім'я файлу в поточному каталозі), тоді $ {1% / *} розширюється до самого імені файлу, і команда cd не працює. Ви можете замість цього використовувати $ (dirname $ 1), який розшириться до '.' в такому разі.
Пауло

Ви праві, оновлені. У мене є проблеми із цитуванням із заміною ${1##*/}на, basenameхоча, не поміщаючи його в окрему змінну, шляхи з пробілами не працюють належним чином.
andsens 02

1
@BryanP Я насправді закінчив переборщити для синхронізатора dotfile, який я підтримую. Він має функцію clean_path (), яка видаляє непотрібні частини шляху, якщо ви приклеїте відносний шлях до кінця $PWDі пройдете
andsens

8

Це робить для мене фокус на OS X: $(cd SOME_DIRECTORY 2> /dev/null && pwd -P)

Це повинно працювати де завгодно. Інші рішення здавались надто складними.


3

на OS X, який ви можете використовувати

stat -f "%N" YOUR_PATH

у Linux у вас може бути realpathвиконуваний файл. якщо ні, то може працювати наступне (не лише для посилань):

readlink -c YOUR_PATH

20
Відповідь OS X не працює. stat -f "%N" PATHдає мені точно такий самий шлях, яким я його пройшов.
Лілі Баллард,

3

Використовуйте readlink -f <relative-path>, наприклад

export FULLPATH=`readlink -f ./`

2

Можливо, це є більш читабельним і не використовує під оболонку і не змінює поточний каталог:

dir_resolve() {
  local dir=`dirname "$1"`
  local file=`basename "$1"`
  pushd "$dir" &>/dev/null || return $? # On error, return error code
  echo "`pwd -P`/$file" # output full, link-resolved path with filename
  popd &> /dev/null
}

1

Існує ще один метод. Ви можете використовувати вбудовування python у скрипт bash, щоб вирішити відносний шлях.

abs_path=$(python3 - <<END
from pathlib import Path
path = str(Path("$1").expanduser().resolve())
print(path)
END
)

0

самостійно редагувати, я щойно помітив, що OP сказав, що він не шукає вирішених символьних посилань:

"Але для написаних мною функцій потрібні абсолютні шляхи, але не вирішені символічні посилання."

Тож здогадайтесь, це зрештою не так відповідає його питанню. :)

Оскільки за ці роки я з цим стикався багато разів, і цього разу мені знадобилася портативна версія pure bash, яку я міг би використовувати на OSX та Linux, я продовжив і написав одну:

Тут живе жива версія:

https://github.com/keen99/shell-functions/tree/master/resolve_path

але задля SO, ось поточна версія (я відчуваю, що вона добре перевірена .. але я відкрита для відгуків!)

Можливо, не складно змусити це працювати для звичайної оболонки Борна (sh), але я не намагався ... Мені занадто подобається $ FUNCNAME. :)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

ось класичний приклад, завдяки brew:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

використовуйте цю функцію, і вона поверне шлях -real-:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn

0

Якщо ваша ОС це підтримує, використовуйте:

realpath "./some/dir"

І використовуючи його у змінній:

some_path="$(realpath "./some/dir")"

Що розширить ваш шлях. Протестовано на Ubuntu та CentOS, можливо, недоступне на ваших. Деякі рекомендують readlink, але в документації для readlink сказано:

Примітка. Realpath (1) - найкраща команда, яка використовується для функціонування канонізації.

Якщо люди задаються питанням, чому я цитую свої змінні, це збереження пробілів у контурах. Як робити, realpath some pathце дасть вам два різні результати шляху. Але realpath "some path"поверне один. Параметри котирування ftw :)


-1

Чи потрібно використовувати виключно bash? Мені потрібно було це зробити, і я набрид від відмінностей між Linux та OS X. Тож я використав PHP для швидкого та брудного рішення.

#!/usr/bin/php <-- or wherever
<?php
{
   if($argc!=2)
      exit();
   $fname=$argv[1];
   if(!file_exists($fname))
      exit();
   echo realpath($fname)."\n";
}
?>

Я знаю, що це не дуже елегантне рішення, але воно працює.


Це не робить нічого, крім повернення шляху до файлу, якщо ви вже знаєте шлях до файлу або файл знаходиться в поточному каталозі ... не знаходить файл у налаштуваннях PATH
G-Man
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.