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

我有一个Bash脚本需要知道它的完整路径。我正在努力找到一种广泛兼容的方式来做到这一点,而不会以相对或看起来很时髦的路径告终。我只需要支持Bash,而不是sh、csh等。

到目前为止我发现:

  1. 从内部获取Bash脚本的源目录解决了通过dirname $0获取脚本路径的问题,这很好,但这可能会返回一个相对路径(如.),如果您想更改脚本中的目录并使路径仍然指向脚本的目录,这将是一个问题。尽管如此,dirname仍将是难题的一部分。

  2. 使用OS X的Bash脚本绝对路径的可接受答案(OS X特定,但答案无论如何都有效)给出了一个函数,该函数将测试$0是否看起来是相对的,如果是,则会预先将$PWD推迟到它。但结果仍然可以有相对位(尽管总体上它是绝对的)-例如,如果脚本是目录/usr/bin中的t,而你在/usr中并且你键入bin/../bin/t来运行它(是的,这很复杂),你最终会得到/usr/bin/../bin作为脚本的目录路径。哪个工作,但是…

  3. readlink解决方案在本页,如下所示:

    # Absolute path to this script. /home/user/bin/foo.shSCRIPT=$(readlink -f $0)# Absolute path this script is in. /home/user/binSCRIPTPATH=`dirname $SCRIPT`

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

所以,有各种各样的方法,但他们都有自己的警告。

什么是更好的方法?“更好”的意思是:

  • 给我绝对的路径。
  • 即使以复杂的方式调用,也会删除时髦的位(参见上面关于#2的注释)。(例如,至少适度规范化路径。)
  • 只依赖于Bash主义或几乎肯定会出现在*nix系统(GNU/Linux、BSD和类似BSD的系统,如OS X等)上的东西。
  • 尽可能避免调用外部程序(例如,更喜欢Bash内置程序)。
  • 更新中,谢谢你的提醒,wich)它不需要解析符号链接(事实上,我更喜欢它让它们单独存在,但这不是必需的)。
647676 次浏览

这是我想出的(编辑:加上sfstewmanLevigrokerKyle Strand罗布·肯尼迪提供的一些调整),这似乎最符合我的“更好”标准:

SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"

SCRIPTPATH行看起来特别迂回,但为了正确处理空格和符号链接,我们需要它而不是SCRIPTPATH=`pwd`

包含输出重定向(>/dev/null 2>&1)处理了罕见的(?)情况,其中cd可能会产生会干扰周围$( ... )捕获的输出。(例如切换到它后的#1被重写为#4一个目录。)

另请注意,深奥的情况,例如执行根本不来自可访问文件系统中的文件的脚本(这是完全可能的),不适合那里(或在我见过的任何其他答案中)。

cd之后和"$0"之前的--是以-开头的目录。

也许对以下问题的公认答案可能会有所帮助。

如何在Mac上获取GNU的readlink-f的行为?

假设您只想将从连接$PWD$0中获得的名称规范化(假设$0一开始不是绝对的),只需沿着abs_dir=${abs_dir//\/.\//\/}等行使用一系列regex替换即可。

是的,我知道这看起来很可怕,但它会起作用,而且是纯粹的Bash。

只是为了它的地狱,我做了一些黑客在一个脚本上做的事情纯粹的文本,纯粹的Bash。我希望我抓住了所有的边缘情况。

请注意,我在另一个答案中提到的${var//pat/repl}不起作用,因为你不能让它只替换最短的匹配,这是替换/foo/../的一个问题,因为例如/*/../将占用它之前的所有内容,而不仅仅是一个条目。由于这些模式不是真正的正则表达式,我不知道如何使其工作。所以这是我想出的非常复杂的解决方案,享受。;)

顺便说一句,如果你发现任何未处理的边缘案件,请告诉我。

#!/bin/bash
canonicalize_path() {local path="$1"OIFS="$IFS"IFS=$'/'read -a parts < <(echo "$path")IFS="$OIFS"
local i=${#parts[@]}local j=0local back=0local -a rev_canonwhile (($i > 0)); do((i--))case "${parts[$i]}" in""|.) ;;..) ((back++));;*) if (($back > 0)); then((back--))elserev_canon[j]="${parts[$i]}"((j++))fi;;esacdonewhile (($j > 0)); do((j--))echo -n "/${rev_canon[$j]}"doneecho}
canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/"

可接受的解决方案(对我来说)不方便成为“可源代码”:
如果你从“source ../../yourScript”调用它,$0将是“bash”!

以下函数(对于bash>=3.0)给了我正确的路径,但是脚本可能会被调用(直接或通过source,使用绝对或相对路径):
(通过“正确的路径”,我的意思是被调用脚本的完整绝对路径,即使从另一条路径调用,直接或使用“source”)

#!/bin/bashecho $0 executed
function bashscriptpath() {local _sp=$1local 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)elseif [[ "$ascript" == "bash" ]] ; thenascript=${BASH_SOURCE[0]}asp="$(dirname $ascript)"fi#echo "b2 asp '$asp', b2 ascript '$ascript'"if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ;elif [[ "${ascript#../}" != "$ascript" ]]; thenasp=$(pwd)while [[ "${ascript#../}" != "$ascript" ]]; doasp=${asp%/*}ascript=${ascript#../}doneelif [[ "${ascript#*/}" != "$ascript" ]];  thenif [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fififieval $_sp="'$asp'"}
bashscriptpath Hexport H=${H}

关键是检测“source”情况并使用${BASH_SOURCE[0]}取回实际脚本。

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

ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"

使用${BASH_SOURCE[0]}而不是$0会产生相同的行为,无论脚本是作为<name>还是source <name>调用。

我很惊讶这里没有提到realpath命令。我的理解是它是广泛可移植/移植的。

您的初始解决方案变成:

SCRIPT=$(realpath "$0")SCRIPTPATH=$(dirname "$SCRIPT")

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

SCRIPT=$(realpath -s "$0")SCRIPTPATH=$(dirname "$SCRIPT")

获取外壳脚本的绝对路径

它在readlink中不使用-f选项,因此它应该可以在BSD/Mac OS X上运行。

支持

  • source./cript(当被.点操作符调用时)
  • 绝对路径 /path/to/script
  • 相对路径,如./脚本
  • /path/dir 1/…/dir 2/dir 3/…/脚本
  • 从Symlink调用时
  • 当符号链接嵌套时,例如)foo->dir1/dir2/bar bar->./../doe doe->script
  • 当调用者更改脚本名称时

我正在寻找此代码不起作用的角落情况.请让我知道。

代码

pushd . > /dev/nullSCRIPT_PATH="${BASH_SOURCE[0]}";while([ -h "${SCRIPT_PATH}" ]); docd "`dirname "${SCRIPT_PATH}"`"SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";donecd "`dirname "${SCRIPT_PATH}"`" > /dev/nullSCRIPT_PATH="`pwd`";popd  > /dev/nullecho "srcipt=[${SCRIPT_PATH}]"echo "pwd   =[`pwd`]"

已知问题

脚本一定在磁盘的某个地方。让它通过网络。如果您尝试从PIPE运行此脚本,它将无法工作

wget -o /dev/null -O - http://host.domain/dir/script.sh |bash

从技术上讲是un定义的,从实践上讲没有合理的方法检测到(协进程不能访问父进程的环境)

由于我的Linux系统上没有默认安装Realpath,因此以下操作适用于我:

SCRIPT="$(readlink --canonicalize-existing "$0")"SCRIPTPATH="$(dirname "$SCRIPT")"

$SCRIPT将包含脚本的真实文件路径,$SCRIPTPATH包含脚本的目录的真实路径。

在使用这个之前,请阅读这个答案的注释。

我今天不得不重新审视这个问题,发现从脚本本身获取Bash脚本的源目录

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

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

用途:

SCRIPT_PATH=$(dirname `which $0`)

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

dirname从文件名中去掉非目录后缀。

因此,无论是否指定了路径,您最终都会得到脚本的完整路径。

我们已经将自己的产品里程路径库放在GitHub上,供社区免费使用。

无耻的插件,但使用这个Bash库,您可以:

get_realpath <absolute|relative|symlink|local file>

这个函数是库的核心:

function get_realpath() {
if [[ -f "$1" ]]then# file *must* existif cd "$(echo "${1%/*}")" &>/dev/nullthen# file *may* not be local# exception is ./file.ext# try 'cd .; cd -;' *works!*local tmppwd="$PWD"cd - &>/dev/nullelse# file *must* be locallocal tmppwd="$PWD"fielse# file *cannot* existreturn 1 # failurefi
# reassemble realpathecho "$tmppwd"/"${1##*/}"return 0 # success
}

它不需要任何外部依赖,只需Bash 4+。还包含get_dirnameget_filenameget_stemnamevalidate_pathvalidate_realpath的函数。它是免费的、干净的、简单的并且有据可查的,所以它也可以用于学习目的,毫无疑问可以改进。跨平台尝试。

更新:经过一些审查和测试,我们已经用实现相同结果的东西替换了上述函数(不使用迪尔名称,只有纯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
}

这还包括一个环境设置no_symlinks,它提供了解析到物理系统的符号链接的能力。默认情况下,它保持符号链接不变。

再次考虑这个问题:在这个线程中引用了一个非常流行的解决方案,其起源这里

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

我一直远离这个解决方案,因为使用了迪尔名称——它可能会带来跨平台的困难,特别是如果出于安全原因需要锁定脚本。但是作为一个纯粹的Bash替代方案,使用:

DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )"

这是一个选择吗?

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

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

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

realpath () {[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"}

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

相关:

简单地说:

BASEDIR=$(readlink -f $0 | xargs dirname)

不需要花哨的操作员。

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

SCRIPT=$( readlink -m $( type -p ${0} ))      # Full path to script handling SymlinksBASE_DIR=`dirname "${SCRIPT}"`                # Directory script is run inNAME=`basename "${SCRIPT}"`                   # Actual name of script even if linked

如果我们使用Bash,我相信这是最方便的方法,因为它不需要调用任何外部命令:

THIS_PATH="${BASH_SOURCE[0]}";THIS_DIR=$(dirname $THIS_PATH)

我已经成功地使用了以下方法一段时间(虽然不是在OS X上),它只使用内置的shell并处理“源foobar.sh”情况。

下面(匆忙组合)示例代码的一个问题是该函数使用了$PWD,这在函数调用时可能是正确的,也可能不是正确的。所以需要处理。

#!/bin/bash
function canonical_path() {# Handle relative vs absolute path[ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1# Change to dirname of xcd ${x%/*}# Combine new pwd with basename of xecho $(pwd -P)/${x##*/}cd $OLDPWD}
echo $(canonical_path "${BASH_SOURCE[0]}")
type [type cdtype echotype pwd

试试这个:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))

另一种方法来做到这一点:

shopt -s extglob
selfpath=$0selfdir=${selfpath%%+([!/])}
while [[ -L "$selfpath" ]];doselfpath=$(readlink "$selfpath")if [[ ! "$selfpath" =~ ^/ ]];thenselfpath=${selfdir}${selfpath}fiselfdir=${selfpath%%+([!/])}done
echo $selfpath $selfdir

一个班轮

`dirname $(realpath $0)`

容易阅读?下面是一个替代方案。它忽略了符号链接

#!/bin/bashcurrentDir=$(cd $(dirname "$0")pwd)
echo -n "current "pwdecho script $currentDir

自从我几年前发布上述答案以来,我的实践已经发展到使用这个linux特定的范例,它可以正确处理符号链接:

ORIGIN=$(dirname $(readlink -f $0))

Bourne shellsh)兼容方式:

SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`

更简单地说,这就是我的工作:

MY_DIR=`dirname $0`source $MY_DIR/_inc_db.sh