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

到目前为止,我发现:

The accepted answer to Getting the source directory of a Bash script from within addresses getting the path of the script via dirname $0, which is fine, but that may return a relative path (like .), which is a problem if you want to change directories in the script and have the path still point to the script's directory. Still, dirname will be part of the puzzle. The accepted answer to Bash script absolute path with OS X (OS X specific, but the answer works regardless) gives a function that will test to see if $0 looks relative and if so will pre-pend $PWD to it. But the result can still have relative bits in it (although overall it's absolute) — for instance, if the script is t in the directory /usr/bin and you're in /usr and you type bin/../bin/t to run it (yes, that's convoluted), you end up with /usr/bin/../bin as the script's directory path. Which works, but... The readlink solution on this page, which looks like this: # 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` But readlink isn't POSIX and apparently the solution relies on GNU's readlink where BSD's won't work for some reason (I don't have access to a BSD-like system to check).

有很多种方法,但都有注意事项。

还有什么更好的办法呢?“更好”的意思是:

Gives me the absolute path. Takes out funky bits even when invoked in a convoluted way (see comment on #2 above). (E.g., at least moderately canonicalizes the path.) Relies only on Bash-isms or things that are almost certain to be on most popular flavors of *nix systems (GNU/Linux, BSD and BSD-like systems like OS X, etc.). Avoids calling external programs if possible (e.g., prefers Bash built-ins). (Updated, thanks for the heads up, wich) It doesn't have to resolve symlinks (in fact, I'd kind of prefer it left them alone, but that's not a requirement).


以下是我所想到的(编辑:加上一些由sfstewman, levigroker, Kyle Strand和Rob Kennedy提供的调整),似乎基本上符合我的“更好”标准:

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

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

包含输出重定向(>/dev/null 2>&1)可以处理罕见的(?)情况,即cd可能产生的输出会干扰周围的$(…)捕捉。(例如cd被覆盖,也ls一个目录后切换到它。)

还要注意一些深奥的情况,比如执行一个根本不是来自可访问文件系统中的文件的脚本(这是完全可能的),不适合在那里(或者在我看到的任何其他答案中)。

cd后面和“$0”之前的——是为了防止目录以-开头。


也许下面这个问题的公认答案会有所帮助。

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

假设您只想规范化从连接$PWD和$0得到的名称(假设$0一开始就不是绝对的),那么只需沿abs_dir=${abs_dir//\/一行使用一系列正则表达式替换即可。\//\/}等。

是的,我知道这看起来很糟糕,但它会起作用,而且是纯粹的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=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/"

被接受的解决方案(对我来说)不方便“来源”: 如果你从“来源../..”/yourScript", $0将是"bash"!

下面的函数(对于bash >= 3.0)给出了正确的路径,但是脚本可能会被调用(直接或通过源代码,使用绝对路径或相对路径): (这里的“正确路径”指的是被调用脚本的完整绝对路径,即使是从另一个路径直接调用,也可以使用“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]}返回实际的脚本。


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

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")

获取shell脚本的绝对路径

它没有在readlink中使用-f选项,因此它应该在BSD/Mac OS X上工作。

支持

source ./script(当被。点运算符) 绝对路径/path/to/script 相对路径,比如。/script /道路/ dir1 / . . / dir2 / dir3 / . . /脚本 当从symlink调用时 当符号链接嵌套eg) foo->dir1/dir2/bar bar->./../能源部doe - >脚本 当调用者更改脚本名称时

我正在寻找这段代码不能工作的极端情况。请让我知道。

Code

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

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


由于realpath没有按默认安装在我的Linux系统上,下面的工作为我:

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

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

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


我今天不得不重新讨论这个问题,并从脚本本身中找到了获取Bash脚本的源目录:

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

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


Use:

SCRIPT_PATH=$(dirname `which $0`)

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

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

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


我们在GitHub上放置了自己的产品realpath-lib,供社区免费使用。

无耻的插头,但有了这个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_dirname, get_filename, get_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,它提供了将符号链接解析到物理系统的能力。默认情况下,它保持符号链接不变。


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

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

我一直没有使用这种解决方案,因为使用了dirname——它可能会带来跨平台的困难,特别是在出于安全原因需要锁定脚本的情况下。但是作为一个纯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变量+文件名参数(不带。/前缀)。

相关:

Bash脚本绝对路径与OS X 从脚本本身中获取Bash脚本的源目录


简单:

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

不需要花哨的运算符。


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

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

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

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

我已经成功地使用了下面的方法一段时间(不是在OS X上),它只使用一个内置的shell,并处理'source foobar.sh'的情况,就我所见。

下面的示例代码的一个问题是,函数使用$PWD,在函数调用时,$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

试试这个:

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

还有另一种方法:

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

一个衬套

`dirname $(realpath $0)`

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

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

echo -n "current "
pwd
echo script $currentDir

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

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

Bourne shell (sh)兼容方式:

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

更简单地说,这对我来说是有效的:

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