Reliable way for a Bash script to get the full path to itself
我有一个bash脚本需要知道它的完整路径。我正试图找到一种广泛兼容的方法来实现这一点,但最终却没有找到相对的或看起来很古怪的路径。我只需要支持bash,而不需要sh、csh等。
到目前为止我所发现的:
从地址中获取bash脚本的源目录的公认答案,通过
接受的对bash脚本绝对路径的回答带有OS X(OS X特定,但不管答案如何都有效)提供了一个函数,该函数将测试
本页中的
1 2 3 4 | # 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` |
但
所以,做这件事的各种方法,但他们都有自己的注意事项。
有什么更好的办法?其中,"更好"是指:
- 给了我绝对路径。
- 即使是以复杂的方式调用,也会去掉一些奇怪的位(参见上面的注释2)。(例如,至少适度规范路径。)
- 只依赖于bash isms或几乎可以确定是最流行的*nix系统(GNU/Linux、BSD和BSD,如OS ;X等系统)的东西。
- 尽可能避免调用外部程序(例如,首选bash内置程序)。
- (更新后,感谢大家的提醒,wich)它不需要解析符号链接(事实上,我更愿意让它们单独使用,但这不是一个要求)。
以下是我的想法(编辑:加上一些由sfstewman、levigroker、kyle strand和rob kennedy提供的调整),这似乎最符合我的"更好"标准:
1 | SCRIPTPATH="$( cd"$(dirname"$0")" ; pwd -P )" |
还要注意,一些深奥的情况,比如执行一个完全不来自可访问文件系统中的文件的脚本(这是完全可能的),在那里(或者在我看到的任何其他答案中)是不适合的。
我很惊讶,这里没有提到
您的初始解决方案是:
1 2 | SCRIPT=`realpath $0` SCRIPTPATH=`dirname $SCRIPT` |
并根据您的喜好保留未解析的符号链接:
1 2 | SCRIPT=`realpath -s $0` SCRIPTPATH=`dirname $SCRIPT` |
我在bash中找到完整规范路径的最简单方法是使用
1 | ABSOLUTE_PATH="$(cd"$(dirname"${BASH_SOURCE[0]}")" && pwd)/$(basename"${BASH_SOURCE[0]}")" |
使用
我今天不得不重新讨论这个问题,发现从内部获取了一个bash脚本的源目录。它还详细阐述了我过去使用过的解决方案。
1 | DIR="$( cd"$( dirname"${BASH_SOURCE[0]}" )" && pwd )" |
链接的答案有更多的变体,例如,对于脚本本身是符号链接的情况。
获取shell脚本的绝对路径
它在readlink中不使用
- source./脚本(由
. 点运算符调用时) - 绝对路径/路径/目标/脚本
- 类似于/script的相对路径
- /路径/dir1/。/dir2/dir3/。/script
- 从symlink调用时
- 当symlink嵌套时,例如)
foo->dir1/dir2/bar bar->./../doe doe->script 。 - 当调用者更改脚本名称时
我在寻找这个代码不起作用的角落案例。请告诉我。
代码1 2 3 4 5 6 7 8 9 10 11 | 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`]" |
已知发行
脚本必须在某个磁盘上。让它通过网络。如果尝试从管道运行此脚本,它将不起作用
1 | wget -o /dev/null -O - http://host.domain/dir/script.sh |bash |
从技术上讲,它是未定义的。实际上,没有一种明智的方法可以检测到这一点。(协同进程无法访问父进程的环境。)
用途:
1 | SCRIPT_PATH=$(dirname `which $0`) |
因此,不管是否指定了路径,最终都会得到脚本的完整路径。
由于在我的Linux系统上未按默认设置安装realpath,因此以下内容适用于我:
1 2 | SCRIPT="$(readlink --canonicalize-existing"$0")" SCRIPTPATH="$(dirname"$SCRIPT")" |
在使用之前,请阅读此答案的注释。
容易阅读?下面是一个备选方案。它忽略符号链接
1 2 3 4 5 6 7 8 9 | #!/bin/bash currentDir=$( cd $(dirname"$0") pwd ) echo -n"current" pwd echo script $currentDir |
自从几年前我发布了上述答案以来,我的实践已经发展到使用这个Linux特定的范例,它可以正确地处理符号链接:
1 | ORIGIN=$(dirname $(readlink -f $0)) |
很晚才回答这个问题,但我使用:
1 2 3 | 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 |
我们已经在Github上放置了自己的产品realpath lib,供免费和无障碍的社区使用。
无耻的插件,但是有了这个bash库,您可以:
1 | get_realpath |
此功能是库的核心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 } |
它不需要任何外部依赖,只需要bash 4+。还包含到validate_path
更新:经过一些审查和测试,我们已经用同样的结果(不使用dirname,只使用pure bash)替换了上述函数,但效率更高:
1 2 3 4 5 6 7 8 | 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 } |
这还包括设置
简单地说:
1 | BASEDIR=$(readlink -f $0 | xargs dirname) |
不需要花哨的操作员。
您可以尝试定义以下变量:
1 | CWD="$(cd -P --"$(dirname --"$0")" && pwd -P)" |
或者可以在bash中尝试以下功能:
1 2 3 | realpath () { [[ $1 = /* ]] && echo"$1" || echo"$PWD/${1#./}" } |
此函数接受一个参数。如果参数已经有绝对路径,则按原样打印,否则打印
相关:
带有OS X的bash脚本绝对路径
从内部获取bash脚本的源目录
Bourne Shell(
1 | SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done` |
接受的解决方案不方便(对我而言)不能"源代码可用":如果你用"EDOCX1"(6),
以下函数(对于bash>=3.0)为我提供了正确的路径,但是可以调用脚本(直接或通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #!/bin/bash echo $0 executed function bashscriptpath() { local _sp=$1 local ascript="$0" local asp="$(dirname $0)" #echo"b1 asp '$asp', b1 ascript '$ascript'" if [["$asp" =="." &&"$ascript" !="bash" &&"$ascript" !="./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}" elif [["$asp" =="." &&"$ascript" =="./.bashrc" ]] ; then asp=$(pwd) else if [["$ascript" =="bash" ]] ; then ascript=${BASH_SOURCE[0]} asp="$(dirname $ascript)" fi #echo"b2 asp '$asp', b2 ascript '$ascript'" if [["${ascript#/}" !="$ascript" ]]; then asp=$asp ; elif [["${ascript#../}" !="$ascript" ]]; then asp=$(pwd) while [["${ascript#../}" !="$ascript" ]]; do asp=${asp%/*} ascript=${ascript#../} done elif [["${ascript#*/}" !="$ascript" ]]; then if [["$asp" =="." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi fi fi eval $_sp="'$asp'" } bashscriptpath H export H=${H} |
关键是检测"
也许下面问题的公认答案是有帮助的。
如何在Mac上获取GNU的readlink-f行为?
假设您只想将$pwd和$0(假设$0不是绝对值)连接后得到的名称规范化,只需沿着
是的,我知道它看起来很可怕,但它会起作用,而且是纯粹的狂欢。
再次考虑这个问题:这个线程中引用了一个非常流行的解决方案,它的起源在这里:
1 | DIR="$( cd"$( dirname"${BASH_SOURCE[0]}" )" && pwd )" |
由于使用了dirname,我没有使用这个解决方案——它可能会带来跨平台的困难,特别是在出于安全原因需要锁定脚本的情况下。但是作为一个纯粹的bash替代方案,如何使用:
1 | DIR="$( cd"$( echo"${BASH_SOURCE[0]%/*}" )" && pwd )" |
这是一个选择吗?
如果我们使用bash,我相信这是最方便的方法,因为它不需要调用任何外部命令:
1 2 | THIS_PATH="${BASH_SOURCE[0]}"; THIS_DIR=$(dirname $THIS_PATH) |
试试这个:
1 | cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0)) |
我已经成功地使用了以下方法一段时间(但不是在OS X上),据我所见,它只使用了一个内置的shell并处理"source foobar.sh"案例。
下面的示例代码(匆忙组合)的一个问题是函数使用$pwd,这在函数调用时可能正确,也可能不正确。所以这需要处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #!/bin/bash function canonical_path() { # Handle relative vs absolute path [ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1 # Change to dirname of x cd ${x%/*} # Combine new pwd with basename of x echo $(pwd -P)/${x##*/} cd $OLDPWD } echo $(canonical_path"${BASH_SOURCE[0]}") type [ type cd type echo type pwd |
一班轮
1 | `dirname $(realpath $0)` |
就为了见鬼,我对一个纯文本、纯bash的脚本进行了一些黑客攻击。我希望我能抓住所有边缘的箱子。
请注意,我在另一个答案中提到的
顺便问一下,如果您发现任何未处理的边缘情况,请告诉我。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #!/bin/bash canonicalize_path() { local path="$1" OIFS="$IFS" IFS=$'/' read -a parts < <(echo"$path") IFS="$OIFS" local i=${#parts[@]} local j=0 local back=0 local -a rev_canon while (($i > 0)); do ((i--)) case"${parts[$i]}" in ""|.) ;; ..) ((back++));; *) if (($back > 0)); then ((back--)) else rev_canon[j]="${parts[$i]}" ((j++)) fi;; esac done while (($j > 0)); do ((j--)) echo -n"/${rev_canon[$j]}" done echo } canonicalize_path"/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/" |
另一种方法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | shopt -s extglob selfpath=$0 selfdir=${selfpath%%+([!/])} while [[ -L"$selfpath" ]];do selfpath=$(readlink"$selfpath") if [[ !"$selfpath" =~ ^/ ]];then selfpath=${selfdir}${selfpath} fi selfdir=${selfpath%%+([!/])} done echo $selfpath $selfdir |
更简单地说,这就是我的工作:
1 2 | MY_DIR=`dirname $0` source $MY_DIR/_inc_db.sh |