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"
- Затем мы получаем часть dirname этого пути (вы можете передать либо dir, либо файл в этот скрипт):
dirname "$1"
- Тогда мы
cd "$(dirname "$1")
в этот относительный каталог и получить абсолютный путь для него, запустивpwd
команда оболочки - После этого мы добавляем basename к абсолютному пути:
$(basename "$1")
- В качестве последнего шага мы
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, но, похоже, он охватывает все или большинство крайних случаев и является очень портативным решением.
- С разрешением символических ссылок:
python -c "import os,sys; print os.path.realpath(sys.argv[1])" path/to/file
- или без него:
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")" "${?}"