Bash脚本获取自身完整路径的可靠方法

T.J*_*der 653 bash path

我有一个Bash脚本需要知道它的完整路径.我试图找到一种广泛兼容的方式来做到这一点,而不会结束相对或时髦的路径.我只需要支持Bash,而不是sh,csh等.

到目前为止我发现了什么:

  1. 地址中获取Bash脚本的源目录的接受答案是获取脚本的路径dirname $0,这很好,但可能会返回相对路径(如.),如果要更改目录中的目录,这是一个问题脚本并使路径仍指向脚本的目录.不过,dirname将成为这个难题的一部分.

  2. OS X的Bash脚本绝对路径 的接受答案(特定于OS X,但答案无论如何)都会给出一个函数,该函数将测试是否$0看起来相对,如果是,则会预先挂起$PWD.但是结果仍然可以包含相对位(虽然总体来说它是绝对的) - 例如,如果脚本t在目录中/usr/bin并且你进入/usr并且你输入bin/../bin/t它来运行它(是的,那是复杂的),你最终会得到/usr/bin/../binas脚本的目录路径.哪个有效,但......

  3. 此页面上readlink解决方案如下所示:

    # Absolute path to this script. /home/user/bin/foo.sh
    SCRIPT=$(readlink -f $0)
    # Absolute path this script is in. /home/user/bin
    SCRIPTPATH=`dirname $SCRIPT`
    
    Run Code Online (Sandbox Code Playgroud)

    但是readlink不是POSIX,显然解决方案依赖于GNU readlink,其中BSD由于某种原因无法工作(我无法访问类似BSD的系统进行检查).

所以,各种方式,但他们都有他们的警告.

什么是更好的方式?"更好"的意思是:

  • 给了我绝对的道路.
  • 即使以复杂的方式调用,也可以获得时髦的位(参见上面#2的评论).(例如,至少要对路径进行适度规范化.)
  • 仅依赖于Bash-isms或几乎肯定会出现在最流行的*nix系统(GNU/Linux,BSD和类似BSD的系统,如OS X等)上的东西.
  • 如果可能,避免调用外部程序(例如,更喜欢Bash内置程序).
  • (更新,感谢抬起头,至极)它没有解决符号链接(其实,我倒是那种喜欢它,离开他们独自一人,但是这不是必须的).

T.J*_*der 541

以下是我的想法(编辑:加上sfstewman,levigroker,Kyle StrandRob Kennedy提供的一些调整),这似乎最符合我的"更好"标准:

SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
Run Code Online (Sandbox Code Playgroud)

SCRIPTPATH条线似乎特别迂回,但我们需要它而不是SCRIPTPATH=`pwd`为了正确处理空格和符号链接.

还要注意,深奥的情况,例如执行一个完全不可能来自可访问文件系统中的文件的脚本(这是完全可能的),不能满足那里(或者我见过的任何其他答案) ).

  • 如果脚本位于`$ PATH`的目录中并且您将其称为"bash scriptname",则此方法无效.在这种情况下,`$ 0`不包含任何路径,只是`scriptname`. (9认同)
  • 我更喜欢你使用`dirname"$ {BASH_SOURCE [0]}"`而不是`dirname"$ 0"`来增加对源脚本的支持. (4认同)
  • -1(请不要过于讨厌它,只是为了让真正的答案猫靠近顶部):我曾经做过类似的事情(我曾用过:`“ $(cd -P” $(dirname “ $ 0”)“ && pwd)”`直到今天,但是Andrew Norrie的答案涵盖了更多情况(即PATH =“ / some / path:$ PATH”; bash“ script_in_path”:仅适用于他的答案,不适用于您的答案(因为$ 0仅包含“ script_in_path”,而没有指示bash在哪里找到它的指示)。正确的是:`ABSOLUTE_PATH =“ $(cd” $(dirname“ $ {BASH_SOURCE [0]}”))“ && pwd )/ $(基本名称“ $ {BASH_SOURCE [0]}”))`(即@ andrew-norrie的答案:涵盖所有情况,imo) (3认同)
  • 最后的调整:`cd -- "$(dirname "$0")"`以避免以连字符开头的目录出现问题。 (2认同)

Dar*_*tle 209

我很惊讶realpath这里没有提到这个命令.我的理解是它广泛便携/移植.

您的初始解决方案变为

SCRIPT=`realpath $0`
SCRIPTPATH=`dirname $SCRIPT`
Run Code Online (Sandbox Code Playgroud)

并根据您的偏好保留未解析的符号链接:

SCRIPT=`realpath -s $0`
SCRIPTPATH=`dirname $SCRIPT`
Run Code Online (Sandbox Code Playgroud)

  • 使用realpath,甚至更短:`SCRIPT_PATH = $(dirname $(realpath -s $ 0))` (11认同)
  • 当给出带有空格的文件名时,许多(如果不是大多数)这些答案都是错误的.`SCRIPT_PATH = $(dirname"$(realpath -s"$ 0")")`是GuySoft答案的固定版本,虽然使用`$ BASH_SOURCE`比`$ 0'更可靠(这取决于'realpath' `,一个非POSIX工具,已安装). (9认同)
  • 在POSIX标准中[未提及](http://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html)。 (3认同)
  • @bolinfest:自[2012年1月](http://git.savannah.gnu.org/cgit/coreutils.git/commit/?id=77ea441f79aa115f79b47d9c1fc9c0004c5c7111)/v8.15以来,realpath是GNU coreutils的一部分.如果您正在运行最近的Linux发行版,其中包括coreutils(基本上应该是所有这些发行版)并且缺少`realpath`,那么打包器肯定会不再将realpath分成另一个包.(当前的coreutils版本是8.21,发布于2013-02-04.我正在运行的版本(在Arch Linux上)似乎是这个,8.21,并且该软件包包含`realpath`,正如任何正确思考的软件包那样;) (2认同)

小智 168

我发现在Bash中获得完整规范路径的最简单方法是使用cdpwd:

ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
Run Code Online (Sandbox Code Playgroud)

使用${BASH_SOURCE[0]},而不是$0不管脚本是否被调用为产生相同的行为<name>source <name>.

  • @over_optimistic我不相信-P在这里有帮助:如果$ 0命名一个符号链接,那么`cd $(dirname $ 0); pwd -P`仍然只是告诉你符号链接所在的目录,而不是实际脚本所在的物理目录.你真的需要在脚本名称上使用类似`readlink`的东西,除了`readlink`实际上不是POSIX,它在操作系统之间似乎有所不同. (6认同)
  • +1在mac上的所有合理场景中都能很好地工作.没有外部依赖关系并在1行中执行.我使用它来获取脚本的目录,如下所示:SCRIPTPATH = $(cd \`dirname"$ {BASH_SOURCE [0]}"\`&& pwd) (6认同)
  • 如果最初指定的文件是符号链接,这似乎不起作用.我认为你需要在循环中使用readlink(或ls)之类的东西,以确保你找到了一个最终的非符号链接文件.我一直在寻找更简洁的东西,但无论如何你可以找到我在Android代码库中使用的最新版本的解决方案,在`dalvik/dx/etc/dx`下. (3认同)

Fel*_*abe 49

我今天不得不重新审视这个问题,并发现从内部获取Bash脚本的源目录.它详细阐述了我过去使用过的解决方案.

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
Run Code Online (Sandbox Code Playgroud)

链接答案中有更多变体,例如脚本本身是符号链接的情况.

  • 我刚刚发现,当直接执行脚本而不是获取脚本时,上述内容不起作用:/sf/ask/1951606121/ (2认同)

Gre*_*Fox 37

获取shell脚本的绝对路径

它不使用-freadlink中的选项,因此它应该适用于BSD/Mac OS X.

支持

  • source ./script(由.点运算符调用时)
  • 绝对路径/路径/到/脚本
  • 像./script这样的相对路径
  • /path/dir1/../dir2/dir3/../script
  • 从符号链接调用时
  • 当符号链接嵌套时,例如) foo->dir1/dir2/bar bar->./../doe doe->script
  • 调用者更改脚本名称时

我正在寻找此代码不起作用的极端情况.请告诉我.

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]); do
    cd "`dirname "${SCRIPT_PATH}"`"
    SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
popd  > /dev/null
echo "srcipt=[${SCRIPT_PATH}]"
echo "pwd   =[`pwd`]"
Run Code Online (Sandbox Code Playgroud)

已知的问题

该脚本必须位于某个磁盘上.让它通过网络.如果您尝试从PIPE运行此脚本,它将无法正常工作

wget -o /dev/null -O - http://host.domain/dir/script.sh |bash
Run Code Online (Sandbox Code Playgroud)

从技术上讲,它是未定义的.实际上,没有理智的方法来检测这一点.(协同进程无法访问父进程的环境.)

  • 正如其他地方所述,并且我认为这不是一个边缘情况,但是readlink -f不是标准参数,并且非常不可用,例如在我的BSD上. (2认同)

Mat*_*att 33

使用:

SCRIPT_PATH=$(dirname `which $0`)
Run Code Online (Sandbox Code Playgroud)

which 打印到标准输出时,在shell提示符下输入传递的参数时将执行的可执行文件的完整路径(这是$ 0包含的内容)

dirname 从文件名中删除非目录后缀.

因此,无论路径是否被指定,您最终都会获得脚本的完整路径.

  • `dirname./ myscript`返回`.`.这可能不是你想要的. (7认同)
  • 不幸的是,这似乎不适用于OS X.如果确实如此,这将是一个很好的解决方案! (3认同)
  • @Matt除非命令在当前路径中,否则我不会使用'which'命令.仅搜索路径中的命令.如果您的脚本不在路径中,则永远不会找到它. (2认同)

ypi*_*pid 24

由于我的Linux系统上没有默认安装realpath,以下内容适用于我:

SCRIPT="$(readlink --canonicalize-existing "$0")"
SCRIPTPATH="$(dirname "$SCRIPT")"
Run Code Online (Sandbox Code Playgroud)

$SCRIPT将包含脚本的实际文件路径以及$SCRIPTPATH包含脚本的目录的实际路径.

在使用之前阅读这个答案的评论.

  • OP已经注意到了这个解决方案而忽略了它不是POSIX - 但这对于基于GNU的系统至少是好的和整洁的.这个的关键特征是**它解析了符号链接**. (3认同)

ger*_*rdw 13

易于阅读?以下是另一种选择.它忽略了符号链接

#!/bin/bash
currentDir=$(
  cd $(dirname "$0")
  pwd
)

echo -n "current "
pwd
echo script $currentDir
Run Code Online (Sandbox Code Playgroud)


Sto*_*oud 10

很晚才回答这个问题,但我使用:

SCRIPT=$( readlink -m $( type -p $0 ))      # Full path to script
BASE_DIR=`dirname ${SCRIPT}`                # Directory script is run in
NAME=`basename ${SCRIPT}`                   # Actual name of script even if linked
Run Code Online (Sandbox Code Playgroud)


Asy*_*abs 8

我们在GitHub上放置了我们自己的产品realpath-lib,用于免费和无阻碍的社区使用.

无耻的插件,但有了这个Bash库你可以:

get_realpath <absolute|relative|symlink|local file>
Run Code Online (Sandbox Code Playgroud)

这个函数是库的核心:

function get_realpath() {

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}
Run Code Online (Sandbox Code Playgroud)

它不需要任何外部依赖,只需要Bash 4+.还包含了这些功能get_dirname,get_filename,get_stemnamevalidate_path validate_realpath.它是免费的,干净的,简单的,文档齐全的,因此它也可以用于学习目的,毫无疑问可以改进.跨平台尝试.

更新:经过一些审查和测试后,我们用一些实现相同结果的东西替换了上面的函数(不使用dirname,只有纯Bash),但效率更高:

function get_realpath() {

    [[ ! -f "$1" ]] && return 1 # failure : file does not exist.
    [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
    echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
    return 0 # success

}
Run Code Online (Sandbox Code Playgroud)

这还包括一个环境设置no_symlinks,可以将符号链接解析到物理系统.默认情况下,它保持符号链接不变.


pou*_*sma 8

只是:

BASEDIR=$(readlink -f $0 | xargs dirname)
Run Code Online (Sandbox Code Playgroud)

不需要花哨的操作员.


ken*_*orb 6

您可以尝试定义以下变量:

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
Run Code Online (Sandbox Code Playgroud)

或者您可以在Bash中尝试以下功能:

realpath () {
  [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
Run Code Online (Sandbox Code Playgroud)

这个函数有一个参数.如果参数已经有绝对路径,则按原样打印,否则打印$PWD变量+文件名参数(不带./前缀).

有关: