Как разрешить символические ссылки в сценарии оболочки
149679 просмотра
18 ответа
Учитывая абсолютный или относительный путь (в Unix-подобной системе), я хотел бы определить полный путь к цели после разрешения любых промежуточных символических ссылок. Бонусные баллы за одновременное разрешение ~ имени пользователя.
Если целью является каталог, возможно, можно выполнить chdir () в каталог, а затем вызвать getcwd (), но я действительно хочу сделать это из сценария оболочки, а не писать помощник по Си. К сожалению, оболочки имеют тенденцию пытаться скрыть существование символических ссылок от пользователя (это bash для OS X):
$ ls -ld foo bar
drwxr-xr-x 2 greg greg 68 Aug 11 22:36 bar
lrwxr-xr-x 1 greg greg 3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$
Что мне нужно, так это функция resol (), которая при выполнении из каталога tmp в приведенном выше примере, resol ("foo") == "/ Users / greg / tmp / bar".
Автор: Greg Hewgill Источник Размещён: 03.09.2019 05:02Ответы (18)
88 плюса
Согласно стандартам, pwd -P
должен возвращать путь с разрешенными символическими ссылками.
Функция C char *getcwd(char *buf, size_t size)
из unistd.h
должна иметь такое же поведение.
377 плюса
readlink -f "$path"
Примечание редактора: вышеописанное работает с GNU readlink
и FreeBSD / PC-BSD / OpenBSD readlink
, но не на OS X по состоянию на 10.11.
GNU readlink
предлагает дополнительные связанные параметры, такие как -m
разрешение символической ссылки, независимо от того, существует ли конечная цель.
Обратите внимание, что начиная с GNU coreutils 8.15 (2012-01-06), существует программа realpath, которая менее тупая и более гибкая, чем описанная выше. Он также совместим с утилитой FreeBSD с тем же именем. Он также включает в себя функциональность для генерации относительного пути между двумя файлами.
realpath $path
[Дополнение администратора ниже от комментария от halloleo - danorton]
Для Mac OS X (не ниже 10.11.x) используйте readlink
без -f
опции:
readlink $path
Примечание редактора: это не приведет к рекурсивному разрешению символических ссылок и, следовательно, не сообщит о конечной цели; например, данная символическая ссылка, на a
которую указывает ссылка b
, которая, в свою очередь, указывает c
только на отчет b
(и не гарантирует, что он будет выводиться как абсолютный путь ).
Используйте следующую perl
команду в OS X, чтобы заполнить пробел отсутствующей readlink -f
функциональности:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"
25 плюса
«pwd -P», кажется, работает, если вы просто хотите каталог, но если по какой-то причине вы хотите имя реального исполняемого файла, я не думаю, что это помогает. Вот мое решение:
#!/bin/bash
# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")
# resolve symlinks
while [[ -h $SELF_PATH ]]; do
# 1) cd to directory of the symlink
# 2) cd to the directory of where the symlink points
# 3) get the pwd
# 4) append the basename
DIR=$(dirname -- "$SELF_PATH")
SYM=$(readlink "$SELF_PATH")
SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done
Автор: tlrobinson
Размещён: 30.03.2009 02:54
16 плюса
Один из моих любимых realpath foo
realpath - вернуть канонизированный абсолютный путь realpath раскрывает все символические ссылки и разрешает ссылки на символы «/./», «/../» и дополнительные символы «/» в строке с нулевым символом в конце, названной path и хранит канонизированный абсолютный путь в буфере размером PATH_MAX, названном resolved_path. Полученный путь не будет иметь символической ссылки, «/ ./» или '/../' компоненты.Автор: Gregory Размещён: 04.12.2008 11:39
10 плюса
readlink -e [filepath]
кажется, что именно то, что вы просите - он принимает произвольный путь, разрешает все символические ссылки и возвращает «реальный» путь - и это «стандартный * nix», который, вероятно, уже есть во всех системах
Автор: Chuck Kollars Размещён: 09.07.2015 02:345 плюса
По-другому:
# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }
# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; }
# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; }
Автор: Keymon
Размещён: 13.09.2011 10:52
5 плюса
Собирая вместе некоторые из приведенных решений, зная, что readlink доступна в большинстве систем, но требует разных аргументов, это хорошо работает для меня в OSX и Debian. Я не уверен насчет систем BSD. Возможно условие должно быть [[ $OSTYPE != darwin* ]]
исключено -f
только из OSX.
#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"
Автор: hpvw
Размещён: 14.11.2016 07:47
2 плюса
Примечание: Я считаю , что это быть твердым, портативный, готовое решение, которое неизменно длительный по той же причине.
Ниже приведен полностью POSIX-совместимый скрипт / функция, который, следовательно, является кроссплатформенным (работает также и в MacOS, чья readlink
поддержка не поддерживается -f
на 10.12 (Sierra)) - он использует только функции языка оболочки POSIX и только POSIX-совместимые вызовы утилит ,
Это переносимая реализация GNUreadlink -e
(более строгая версия readlink -f
).
Вы можете запустить скрипт сsh
или подключите функцию в bash
, ksh
иzsh
:
Например, внутри скрипта вы можете использовать его следующим образом, чтобы получить истинный каталог происхождения скрипта запуска с разрешенными символическими ссылками:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
определение скрипта / функции:
Код был адаптирован с благодарностью от этого ответа .
Я также создал здесьbash
версию автономной утилиты на основе , которую вы можете установить , если у вас установлен Node.js.
npm install rreadlink -g
#!/bin/sh
# SYNOPSIS
# rreadlink <fileOrDirPath>
# DESCRIPTION
# Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
# prints its canonical path. If it is not a symlink, its own canonical path
# is printed.
# A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
# - Won't work with filenames with embedded newlines or filenames containing
# the string ' -> '.
# COMPATIBILITY
# This is a fully POSIX-compliant implementation of what GNU readlink's
# -e option does.
# EXAMPLE
# In a shell script, use the following to get that script's true directory of origin:
# trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.
target=$1 fname= targetDir= CDPATH=
# Try to make the execution environment as predictable as possible:
# All commands below are invoked via `command`, so we must make sure that
# `command` itself is not redefined as an alias or shell function.
# (Note that command is too inconsistent across shells, so we don't use it.)
# `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not
# even have an external utility version of it (e.g, Ubuntu).
# `command` bypasses aliases and shell functions and also finds builtins
# in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
# that to happen.
{ \unalias command; \unset -f command; } >/dev/null 2>&1
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.
while :; do # Resolve potential symlinks until the ultimate target is found.
[ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
fname=$(command basename -- "$target") # Extract filename.
[ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
if [ -L "$fname" ]; then
# Extract [next] target path, which may be defined
# *relative* to the symlink's own directory.
# Note: We parse `ls -l` output to find the symlink target
# which is the only POSIX-compliant, albeit somewhat fragile, way.
target=$(command ls -l "$fname")
target=${target#* -> }
continue # Resolve [next] symlink target.
fi
break # Ultimate target reached.
done
targetDir=$(command pwd -P) # Get canonical dir. path
# Output the ultimate target's canonical path.
# Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
if [ "$fname" = '.' ]; then
command printf '%s\n' "${targetDir%/}"
elif [ "$fname" = '..' ]; then
# Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
# AFTER canonicalization.
command printf '%s\n' "$(command dirname -- "${targetDir}")"
else
command printf '%s\n' "${targetDir%/}/$fname"
fi
)
rreadlink "$@"
Касательная к безопасности:
jarno , ссылаясь на функцию, гарантирующую, что встроенная функция command
не скрывается псевдонимом или функцией оболочки с тем же именем, спрашивает в комментарии:
Что если
unalias
илиunset
и[
установлены как псевдонимы или функции оболочки?
Мотивация rreadlink
обеспечения того, чтобы он command
имел первоначальное значение, состоит в том, чтобы использовать его для обхода (мягких) вспомогательных псевдонимов и функций, часто используемых для теневого копирования стандартных команд в интерактивных оболочках, таких как переопределение ls
для включения избранных параметров.
Я думаю , что можно с уверенностью сказать , что если вы имеете дело с ненадежной, злонамеренной средой, заботясь о том unalias
или unset
- или, если на то пошли, while
, do
, ... - пересматриваются не является проблемой.
Есть нечто , на что функция должна полагаться, чтобы иметь свое первоначальное значение и поведение - нет никакого способа обойти это.
То, что POSIX-подобные оболочки позволяют переопределять встроенные и даже языковые ключевые слова, по своей сути является угрозой безопасности (и вообще, писать параноидальный код сложно).
Чтобы конкретно решить ваши проблемы:
Функция опирается unalias
и unset
имеет свое первоначальное значение. Было бы проблемой переопределить их как функции оболочки таким образом, чтобы это изменило их поведение; переопределение в качестве псевдонима не обязательно является проблемой, потому что цитирование (часть) имени команды (например, \unalias
) обходит псевдонимы.
Однако, ссылаясь на это не вариант для оболочки ключевых слов ( while
, for
, if
, do
, ...) , и в то время как ключевые слова оболочки делают имеют приоритет над оболочками функций , в bash
и zsh
псевдонимами имеют наивысший приоритет, поэтому для защиты от снарядов, ключевое слово переопределениях вы должны работать unalias
с их имена (хотя в неинтерактивных bash
оболочках (таких как сценарии) псевдонимы по умолчанию не раскрываются - только если shopt -s expand_aliases
явно вызывается первым).
Чтобы гарантировать, что unalias
- как встроенный - имеет первоначальное значение, вы должны \unset
сначала использовать его, что требует, чтобы оно unset
имело первоначальное значение:
unset
это встроенная оболочка , поэтому для того, чтобы она вызывалась как таковая, вам необходимо убедиться, что она сама по себе не переопределена как функция . Хотя вы можете обойти форму псевдонима с кавычками, вы не можете обойти форму функции оболочки - поймать 22.
Таким образом, если вы не можете полагаться на то, unset
чтобы иметь его первоначальное значение, насколько я могу судить, не существует гарантированного способа защиты от всех злонамеренных переопределений.
1 плюс
Обычные сценарии оболочки часто должны находить свой «домашний» каталог, даже если они вызываются как символическая ссылка. Таким образом, скрипт должен найти свою «реальную» позицию всего за $ 0.
cat `mvn`
В моей системе печатается скрипт, содержащий следующее, что должно быть хорошим указанием на то, что вам нужно.
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
Автор: Hugo
Размещён: 11.08.2008 11:17
1 плюс
function realpath {
local r=$1; local t=$(readlink $r)
while [ $t ]; do
r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
t=$(readlink $r)
done
echo $r
}
#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..
Автор: Dave
Размещён: 10.04.2014 02:52
1 плюс
Попробуй это:
cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
Автор: diyism
Размещён: 29.08.2014 02:41
1 плюс
Так как я сталкивался с этим много раз на протяжении многих лет, и в этот раз мне понадобилась чисто переносимая версия bash, которую я мог бы использовать для OSX и Linux, я решил написать одну:
Живая версия живет здесь:
https://github.com/keen99/shell-functions/tree/master/resolve_path
но ради SO, вот текущая версия (я чувствую, что это хорошо проверено .. но я открыт для обратной связи!)
Может быть нетрудно заставить его работать на простой оболочке 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
Автор: keen
Размещён: 24.12.2014 09:06
1 плюс
Чтобы обойти несовместимость Mac, я придумал
echo `php -r "echo realpath('foo');"`
Не отлично, но кросс-ОС
Автор: Clemens Tolboom Размещён: 08.08.2014 08:501 плюс
Вот как можно получить фактический путь к файлу в MacOS / Unix, используя встроенный скрипт Perl:
FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")
Аналогично, чтобы получить каталог с символьным файлом:
DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")
Автор: Igor Afanasyev
Размещён: 30.01.2018 03:57
1 плюс
Ваш путь - это каталог, или это может быть файл? Если это каталог, все просто:
(cd "$DIR"; pwd -P)
Однако, если это может быть файл, то это не сработает:
DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"
потому что символическая ссылка может преобразоваться в относительный или полный путь.
В сценариях мне нужно найти реальный путь, чтобы я мог ссылаться на конфигурацию или другие сценарии, установленные вместе с ним, я использую это:
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
Вы можете установить SOURCE
любой путь к файлу. По сути, пока путь является символической ссылкой, он разрешает эту символическую ссылку. Хитрость в последней строке цикла. Если разрешенная символическая ссылка является абсолютной, она будет использовать это как SOURCE
. Однако, если он является относительным, он будет предшествовать DIR
для него, который был преобразован в реальное местоположение с помощью простого трюка, который я впервые описал.
0 плюса
Я считаю, что это истинный и окончательный «способ разрешения символической ссылки», будь то каталог или не каталог, используя Bash:
function readlinks {(
set -o errexit -o nounset
declare n=0 limit=1024 link="$1"
# If it's a directory, just skip all this.
if cd "$link" 2>/dev/null
then
pwd -P "$link"
return 0
fi
# Resolve until we are out of links (or recurse too deep).
while [[ -L $link ]] && [[ $n -lt $limit ]]
do
cd "$(dirname -- "$link")"
n=$((n + 1))
link="$(readlink -- "${link##*/}")"
done
cd "$(dirname -- "$link")"
if [[ $n -ge $limit ]]
then
echo "Recursion limit ($limit) exceeded." >&2
return 2
fi
printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}
Обратите внимание, что все cd
и set
все происходит в подоболочке.
0 плюса
Здесь я представляю то, что я считаю кроссплатформенным (по крайней мере, Linux и macOS) решением для ответа, который в настоящее время хорошо работает для меня.
crosspath()
{
local ref="$1"
if [ -x "$(which realpath)" ]; then
path="$(realpath "$ref")"
else
path="$(readlink -f "$ref" 2> /dev/null)"
if [ $? -gt 0 ]; then
if [ -x "$(which readlink)" ]; then
if [ ! -z "$(readlink "$ref")" ]; then
ref="$(readlink "$ref")"
fi
else
echo "realpath and readlink not available. The following may not be the final path." 1>&2
fi
if [ -d "$ref" ]; then
path="$(cd "$ref"; pwd -P)"
else
path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
fi
fi
fi
echo "$path"
}
Вот решение для MacOS (только?). Возможно, лучше подходит для первоначального вопроса.
mac_realpath()
{
local ref="$1"
if [[ ! -z "$(readlink "$ref")" ]]; then
ref="$(readlink "$1")"
fi
if [[ -d "$ref" ]]; then
echo "$(cd "$ref"; pwd -P)"
else
echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
fi
}
Автор: RuneImp
Размещён: 14.01.2019 10:32
0 плюса
Мой ответ здесь Bash: как получить реальный путь символической ссылки?
но вкратце очень удобно в скриптах:
script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home
Они являются частью GNU coreutils и подходят для использования в системах Linux.
Чтобы проверить все, мы помещаем символическую ссылку в / home / test2 /, исправляем некоторые дополнительные вещи и запускаем / вызываем ее из корневого каталога:
/$ /home/test2/symlink
/home/test
Original script home: /home/test
куда
Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink
Автор: Arunas Bartisius
Размещён: 03.09.2019 02:01
Вопросы из категории :
- bash Как разрешить символические ссылки в сценарии оболочки
- bash Удалить все, кроме самых последних файлов X в Bash
- bash Распараллелить скрипт Bash с максимальным количеством процессов
- bash Как мне написать цикл для Bash
- bash Как обнулить числа в именах файлов в Bash?
- shell Вызов команд оболочки из Ruby
- shell Проверьте, существует ли каталог в сценарии оболочки
- shell Как я могу отправить стандартный вывод одного процесса нескольким процессам, используя (желательно безымянные) каналы в Unix (или Windows)?
- shell Как подавить прекращение сообщения после убийства в bash?
- scripting Удалить все таблицы, имена которых начинаются с определенной строки
- scripting В SQL Server, как мне сгенерировать оператор CREATE TABLE для данной таблицы?
- scripting Как я могу получить путь и имя файла, который выполняется в настоящее время?
- symlink Есть ли способ проверить, есть ли символические ссылки, указывающие на каталог?
- symlink Как получить / установить логический путь к каталогу в python
- symlink В чем разница между символической ссылкой и жесткой ссылкой?
- symlink Как копировать символические ссылки в Perl?