在bash中扩展可能的相对路径

105
作为我的脚本的参数,有一些文件路径。这些路径可以是相对路径(或包含~)。但是,对于我编写的函数,我需要绝对路径,但不需要解析它们的符号链接。
是否有任何此功能的函数?

2
尝试使用readlink函数 - http://andy.wordpress.com/2008/05/09/bash-equivalent-for-php-realpath/ - arunkumar
7
readlink 解析符号链接;但 OP 不希望这样。 - Keith Thompson
可能是将相对路径转换为绝对路径的重复问题。 - tripleee
可能是重复的问题:bash/fish命令打印文件的绝对路径 - Trevor Boyd Smith
1
echo $(cd some_directory && pwd),不解析符号链接,如果some_directory不存在则失败。工作目录不受影响。或者将其赋值给变量MY_PATH=$(cd some_directory && pwd) - qeatzy
或者按照readlink文档的建议使用realpath -s "./some/dir"。快速,简单,没有任何函数(如果您的操作系统支持的话,应该会?)就像我的答案所说的那样。 https://dev59.com/Nmw05IYBdhLWcg3wnjPC#64706902 - Raid
11个回答

88

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 - andsens
9
要在OS X上获得“readlink -f”功能,可以使用Homebrew,然后运行:$ brew install coreutils,接着可以使用 greadlink -f - jgpawletko

53

2
遗憾的是,这确实是这里最好的答案,但它没有得到应有的赞同。+1 因为准确回答了 OP 的问题。他想要未解析的符号链接,这实际上是关于 bash 的本地 DIRSTACK 的问题,而不是其他什么。很好的解决方案! - cptstubing06
7
当需要达到标准的sh兼容性时,pushd; cd <dir>; <cmd>; popd 可以替换为 (cd <dir>; <cmd>) - Abbafei

20

如果您的操作系统支持,可以使用:

realpath -s "./some/dir"

并将其存储在变量中:

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

这会扩展你的路径。在Ubuntu和CentOS上进行了测试,可能在你的系统上不可用。一些人推荐使用readlink,但是readlink的文档中指出:

请注意,realpath(1) 是首选的用于规范化功能的命令。

如果有人想知道为什么要引用变量,那是为了保留路径中的空格。像执行 realpath some path 会得到两个不同的路径结果,但 realpath "some path" 只会返回一个结果。引用参数更胜一筹 :)

感谢 NyanPasu64 提供的信息。如果您不希望其跟随符号链接,则需要添加 -s


迄今为止最好的答案。其他所有答案要么忽略了不遵循符号链接的要求,要么太过复杂。 - swineone
在Linux上,realpath会遵循符号链接,而OP明确要求不要这样做。 - nyanpasu64
1
感谢 @nyanpasu64 的提醒,我编辑了答案并加入了不跟随符号链接的内容。 - Raid

18

简单的一行代码:

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
不需要。请注意括号,它们会导致其中的命令在子shell中运行。子shell的状态(例如工作目录)不会泄漏到其父shell。在这里阅读更多信息:http://tldp.org/LDP/abs/html/subshells.html。 - andsens
1
不错的一行代码。但是有一个问题:如果$1的扩展值没有任何斜杠(即它是当前目录中的文件名),那么${1%/*}会扩展为文件名本身,cd命令将失败。在这种情况下,您可能希望改用$(dirname $1),它将扩展为“。”。 - Paulo
1
@BryanP 我最终为我正在维护的点文件同步器做了完全过度的工作。它有一个clean_path()函数,可以删除不需要的路径部分,如果你将相对路径放在$PWD的末尾并通过该函数运行它,它应该能够很好地工作:https://github.com/andsens/homeshick/blob/490013f818e26fa477141846614ea873d5ef645f/lib/fs.sh#L66-L152 - andsens
1
如果您不想使用函数而想快速简单地完成此操作,那么可以根据 https://dev59.com/Nmw05IYBdhLWcg3wnjPC#64706902 使用 realpath -s "./some/dir" - Raid
显示剩余9条评论

9
这是我在OS X上的解决方法:$(cd SOME_DIRECTORY 2> /dev/null && pwd -P)。这个方法在任何地方都适用。其他解决方案看起来太复杂了。

5

使用readlink -f <相对路径>命令,例如:

export FULLPATH=`readlink -f ./`

3
也许这种方法更易读,不使用子shell,也不改变当前目录:
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
}

2

还有另一种方法。您可以在Bash脚本中使用Python嵌入来解析相对路径。

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

2
在OS X上,您可以使用
stat -f "%N" YOUR_PATH

在Linux上,您可能有realpath可执行文件。如果没有,以下方法可能适用(不仅适用于链接):

readlink -c YOUR_PATH

24
OS X的答案不起作用。stat -f“%N”路径 给我返回了与我提供的路径完全相同的路径。 - Lily Ballard

0

自我编辑,我刚注意到OP说他不想要符号链接被解析:

“但是对于我编写的函数,我需要绝对路径,但是它们没有解析其符号链接。”

所以猜想这对他的问题来说并不太合适。 :)

由于多年来我遇到了这种情况很多次,而这一次我需要一个纯bash便携版本,可以在OSX和Linux上使用,因此我去写了一个:

最新版本在此处:

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

但为了SO的缘故,这是当前版本(我觉得它经过了充分的测试...但我乐意听取反馈!)

也许将其适用于普通的Bourne Shell(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

使用此函数将返回真实路径:
%% 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接