Расширить возможный относительный путь в bash

82

В качестве аргументов моего скрипта есть несколько путей к файлам. Они, конечно, могут быть относительными (или содержать ~). Но для функций, которые я написал, мне нужны абсолютные пути, но не разрешенные символические ссылки.

Есть ли для этого какая-нибудь функция?

лаар
источник
1
Попробуйте функцию readlink - andy.wordpress.com/2008/05/09/bash-equivalent-for-php-realpath
arunkumar
7
readlinkразрешает символические ссылки; ОП этого не хочет.
Кейт Томпсон
возможный дубликат команды bash / fish для вывода абсолютного пути к файлу
Тревор Бойд Смит
1
echo $(cd some_directory && pwd), не разрешать символическую ссылку, завершается ошибкой, если some_directory не существует. рабочий каталог не затронут. или присвоить переменной 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 03
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 за точный ответ на вопрос ОП. Он хочет, чтобы символические ссылки не разрешались, что на самом деле является вопросом локального bash DIRSTACKи ничего больше. Хорошее решение!
cptstubing06,
5
Если вы стремитесь к стандартной shсовместимости, pushd; cd <dir>; <cmd>; popdего можно заменить на (cd <dir>; <cmd>).
Abbafei
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

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

andsens
источник
1
Не изменит ли это текущий рабочий каталог? Стоит ли cd -потом его сбросить?
devios1
4
Незачем. Обратите внимание на скобки, они заставляют внутренние команды выполняться в подоболочке. Состояние (например, рабочий каталог) подоболочки не передается в ее родительскую оболочку. Подробнее читайте здесь: tldp.org/LDP/abs/html/subshells.html
andsens
1
Хороший однострочный. Однако есть проблема: если в развернутом значении $ 1 нет косой черты (т.е. это имя файла в текущем каталоге), тогда $ {1% / *} заменяется на само имя файла, и команда cd не выполняется. Вместо этого вы можете использовать $ (dirname $ 1), которое будет заменено на '.' в этом случае.
Пауло
Вы правы, обновлено. У меня есть некоторые проблемы с цитированием при замене ${1##*/}на, basenameхотя, без помещения его в отдельную переменную, пути с пробелами не работают должным образом.
andsens 02
1
@BryanP В конечном итоге я полностью переборщил с синхронизатором точечных файлов, который я поддерживаю. У него есть функция clean_path (), которая удаляет ненужные части пути, если вы вставите относительный путь в конец $PWDи пропустите его через него, он должен работать достаточно хорошо: github.com/andsens/homeshick/blob/…
andsens
8

Это помогает мне в OS X: $(cd SOME_DIRECTORY 2> /dev/null && pwd -P)

Он должен работать где угодно. Остальные решения казались слишком сложными.

Майки
источник
4

в 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 ./`
Isapir
источник
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
}
sja
источник
1

Есть другой способ. Вы можете использовать встраивание python в сценарий bash для разрешения относительного пути.

abs_path=$(python3 - <<END
from pathlib import Path
path = str(Path("$1").expanduser().resolve())
print(path)
END
)
cy8g3n
источник
0

Самостоятельное редактирование, я только что заметил, что OP сказал, что не ищет разрешенных символических ссылок:

«Но для функций, которые я написал, мне нужны абсолютные пути, но не разрешенные символические ссылки».

Так что угадайте, что это не совсем так по поводу его вопроса. :)

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

Здесь живет живая версия:

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

но ради ТАК, вот текущая версия (я считаю, что она хорошо протестирована ... но я открыт для отзывов!)

Может быть нетрудно заставить его работать с простой оболочкой bourne (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