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

时间:2011-01-23 13:24:17

标签: bash path

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

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

  1. Getting the source directory of a Bash script from within 的接受答案通过dirname $0解决了脚本的路径,这很好,但可能会返回 relative < / em> path(如.),如果要更改脚本中的目录并使路径仍指向脚本的目录,则会出现问题。尽管如此,dirname将成为这个难题的一部分。

  2. Bash script absolute path with OS X 的已接受答案(特定于OS X,但答案无论如何)提供了一个函数,该函数将测试是否{ {1}}看起来是相对的,如果是的话会预先$0。但是结果仍然可以包含相对位(尽管总体上它是绝对的) - 例如,如果脚本在$PWD目录中t并且您在/usr/bin并且键入了/usr运行它(是的,那是错综复杂的),最终以bin/../bin/t作为脚本的目录路径。哪个有效,但是......

  3. /usr/bin/../bin解决方案on this page,如下所示:

    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` 不是POSIX,显然解决方案依赖于GNU的readlink,其中BSD由于某种原因无法工作(我无法访问类似BSD的系统进行检查)

  4. 所以,各种方法,但他们都有他们的警告。

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

    • 给了我绝对的道路。
    • 即使以复杂的方式调用,也要取出时髦的部分(参见上面#2的评论)。 (例如,至少适度规范化道路。)
    • 仅依赖于Bash-isms或几乎肯定会出现在最流行的* nix系统(GNU / Linux,BSD和类似BSD的系统,如OS X等)上的东西。
    • 尽可能避免调用外部程序(例如,更喜欢Bash内置程序)。
    • 已更新,感谢您的提醒,wich)它不需要解决符号链接(事实上,我更喜欢它让他们独自一人,但那是不是要求)。

23 个答案:

答案 0 :(得分:472)

以下是我提出的内容(编辑:加上sfstewmanlevigrokerKyle StrandRob Kennedy提供的一些调整),这些调整似乎最适合我的“更好”标准:

SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"

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

另请注意,深奥的情况,例如执行一个根本不是来自可访问文件系统中的文件的脚本(这是完全可能的),也不能满足(或者在任何其他答案中)已经看过了。

答案 1 :(得分:185)

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

您的初始解决方案变为:

SCRIPT=`realpath $0`
SCRIPTPATH=`dirname $SCRIPT`

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

SCRIPT=`realpath -s $0`
SCRIPTPATH=`dirname $SCRIPT`

答案 2 :(得分:148)

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

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

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

答案 3 :(得分:40)

我今天不得不重新审视此问题并找到 Get the source directory of a Bash script from within the script itself

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

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

答案 4 :(得分:36)

获取shell脚本的绝对路径

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

支架

  • source ./script(由.点运算符调用时)
  • 绝对路径/路径/到/脚本
  • 相对路径,如./script
  • /路径/ DIR1 /../ DIR2 / DIR3 /../脚本
  • 从symlink调用时
  • 当符号链接嵌套时,例如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`]"

已知的问题

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

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

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

答案 5 :(得分:31)

使用:

SCRIPT_PATH=$(dirname `which $0`)

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

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

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

答案 6 :(得分:25)

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

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

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

在使用本文之前,请阅读this answer的评论。

答案 7 :(得分:13)

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

override func loadView() {
}

func zoomToUser(sender: UIButton!) {
            mapView.showsUserLocation = true
        }

自从几年前我发布了上述答案以来,我已经将我的实践演变为使用这种适当处理符号链接的特定于Linux的范例:

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

echo -n "current "
pwd
echo script $currentDir

答案 8 :(得分:11)

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

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

答案 9 :(得分:8)

我们已将自己的产品realpath-lib放置在GitHub上,以供社区免费和无阻碍使用。

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

get_realpath <absolute|relative|symlink|local file>

此功能是库的核心:

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+。还包含get_dirnameget_filenameget_stemname validate_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

}

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

答案 10 :(得分:7)

简单地:

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

不需要花哨的操作员。

答案 11 :(得分:5)

Bourne shellsh)符合方式:

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

答案 12 :(得分:5)

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

CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"

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

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

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

相关:

答案 13 :(得分:3)

或许接受以下问题的答案可能会有所帮助。

How can I get the behavior of GNU's readlink -f on a Mac?

鉴于你只想规范化连接$ PWD和$ 0的名称(假设$ 0不是绝对的开头),只需在abs_dir=${abs_dir//\/.\//\/}行使用一系列正则表达式替换即可

是的,我知道它看起来很糟糕,但它会起作用并且是纯粹的Bash。

答案 14 :(得分:3)

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

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

由于使用了dirname,我远离了这个解决方案 - 它可能会出现跨平台的困难,特别是出于安全原因需要锁定脚本时。但作为一个纯粹的Bash替代品,如何使用:

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

这会是一个选择吗?

答案 15 :(得分:3)

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

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

#!/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}

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

答案 16 :(得分:2)

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

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

答案 17 :(得分:1)

试试这个:

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

答案 18 :(得分:0)

我已成功使用以下方法一段时间(虽然不是在OS X上),它只使用内置的shell并处理'source 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 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

答案 19 :(得分:0)

一个班轮

`dirname $(realpath $0)`

答案 20 :(得分:0)

只是为了它的地狱我已经做了一些黑客攻击一个纯文本的东西,纯粹在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=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}}

答案 21 :(得分:-1)

另一种方法:

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

答案 22 :(得分:-4)

更简单地说,这对我有用:

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