给定相对路径,是否有检索绝对路径的命令?
例如,我希望 $line 包含 dir 中每个文件的绝对路径./etc/
find ./ -type f | while read line; do
echo $line
done
试试realpath
。
~ $ sudo apt-get install realpath # may already be installed
~ $ realpath .bashrc
/home/username/.bashrc
为避免扩展符号链接,请使用realpath -s
.
答案来自“ bash/fish 命令打印文件的绝对路径”。
如果您安装了 coreutils 包,您通常可以使用readlink -f relative_file_name
它来检索绝对包(解决所有符号链接)
#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
UPD一些解释
"$1"
dirname "$1"
cd "$(dirname "$1")
获取它的绝对路径pwd
$(basename "$1")
echo
我们利用:
find "$(pwd)"/ -type f
获取所有文件或
echo "$(pwd)/$line"
显示完整路径(如果相对路径很重要)
对于它的价值,我投票支持选择的答案,但想分享一个解决方案。缺点是,它仅适用于 Linux - 在堆栈溢出之前,我花了大约 5 分钟试图找到 OSX 等价物。我确定它在那里。
在 Linux 上,您可以readlink -e
与dirname
.
$(dirname $(readlink -e ../../../../etc/passwd))
产量
/etc/
然后你使用dirname
'sisterbasename
来获取文件名
$(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\")"
旁注,您当前的工作目录不受影响。
恕我直言,最好的解决方案是此处发布的解决方案:https ://stackoverflow.com/a/3373298/9724628 。
它确实需要 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/..
.
如果你在 Mac OS X 上使用 bash,它既不存在realpath也不它的readlink可以打印绝对路径,你可以选择编写你自己的版本来打印它。这是我的实现:
(纯重击)
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
在 的情况下find
,最简单的方法可能是只给出搜索的绝对路径,例如:
find /etc
find `pwd`/subdir_of_current_dir/ -type f
类似于@ernest-a 的答案,但不影响$OLDPWD
或定义新功能,您可以触发子外壳(cd <path>; pwd)
$ pwd
/etc/apache2
$ cd ../cups
$ cd -
/etc/apache2
$ (cd ~/..; pwd)
/Users
$ cd -
/etc/cups
我发现Eugen Konkov 的答案是最好的,因为它不需要安装任何程序。但是,对于不存在的目录,它将失败。
我编写了一个适用于不存在的目录的函数:
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
}
dirname
它通过向上遍历直到cd
成功,然后返回当前目录以及被删除的所有内容来解决不存在目录的问题dirname
。
他们所说的,除了find $PWD
或(在 bash 中)find ~+
更方便一些。
我找不到可以在 Mac OS Catalina、Ubuntu 16 和 Centos 7 之间完美移植的解决方案,所以我决定使用 python inline 来完成它,它适用于我的 bash 脚本。
to_abs_path() {
python -c "import os; print os.path.abspath('$1')"
}
to_abs_path "/some_path/../secrets"
您可以对任何相对路径 $line 使用 bash 字符串替换:
line=$(echo ${line/#..\//`cd ..; pwd`\/})
line=$(echo ${line/#.\//`pwd`\/})
echo $line
基本的字符串前面替换遵循公式
${string/#substring/replacement}
在这里很好地讨论:https ://www.tldp.org/LDP/abs/html/string-manipulation.html
当我们希望它成为我们查找/替换的字符串的一部分时,该\
字符否定了。/
echo "mydir/doc/ mydir/usoe ./mydir/usm" | awk '{ split($0,array," "); for(i in array){ system("cd "array[i]" && echo $PWD") } }'
如果相对路径是目录路径,那么试试我的,应该是最好的:
absPath=$(pushd ../SOME_RELATIVE_PATH_TO_Directory > /dev/null && pwd && popd > /dev/null)
echo $absPath
这是一个相当短的函数,可用于仅使用 POSIX shell 和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
将返回错误。这恰好是readlink -f
它试图模仿的 GNU/Linux 命令的确切语义。
在 bash/zsh 中,您还可以将数字列表压缩为 just{1..8}
或类似。选择 8 的数量是因为这是多年来 Linux 中的最大限制,然后在 4.2 版中将其更改为整个路径的 40 分辨率的总限制。如果达到分辨率限制,代码将不会失败,而是返回最后考虑的路径——[ -L "${FILEPATH}" ]
然而,可以添加显式检查来检测这种情况。
通过简单地删除函数和子shell包装器,此代码也可以轻松地重用,以确保当前工作目录与文件系统中执行脚本的位置匹配(shell脚本的常见要求):
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}"
如果要将包含相对路径的变量转换为绝对路径,则可以:
dir=`cd "$dir"`
"cd" 在不改变工作目录的情况下回显,因为在子 shell 中执行。
这是所有其他解决方案的链式解决方案,例如,当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")" "${?}"
基于@EugenKonkov的这个回答和@HashChange的这个回答,我的回答结合了前者的简洁性和后者的处理。我相信下面的所有选项都依赖于基本的Shell 命令语言 POSIX 标准。.
..
使用dirname
and basename
,一个选项是:
absPathDirname()
{
[ -d "${1}" ] && set -- "${1}" || set -- "`dirname "${1}"`" "/`basename "${1}"`"
echo "`cd "${1}"; pwd`${2}";
}
不使用dirname
or basename
,另一个简短的选项是:
absPathMinusD()
{
[ -d "${1}" ] && set -- "${1}" || set -- "${1%${1##*/}}" "/${1##*/}"
echo "`cd "${1:-.}"; pwd`${2}";
}
我会推荐上面两个选项之一,其余的只是为了好玩......
grep 版本:
absPathGrep()
{
echo "`[ "${1##/*}" ] && echo "$1" | grep -Eo '^(.*/)?\.\.($|/)' | { read d && cd "$d"; echo "${PWD}/${1#$d}"; } || echo "$1"`"
}
作为“使用 shell 的有限 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"
}