首页 文章

Bash脚本获取自身完整路径的可靠方式[重复]

提问于
浏览
543

这个问题在这里已有答案:

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

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

  • Getting the source directory of a Bash script from within地址的接受答案是通过 dirname $0 获取脚本的路径,这很好,但是可能会返回相对路径(如 . ),如果要更改脚本中的目录并使路径保持不变,这是一个问题指向脚本的目录 . 尽管如此, dirname 将成为这个难题的一部分 .

  • Bash script absolute path with OS X的已接受答案(特定于OS X,但答案无论如何都有效)会给出一个函数,该函数将测试 $0 是否看起来相对,如果是,则会将 $PWD 预先挂起 . 但结果仍然可以有相对位(虽然总体来说它是绝对的) - 例如,如果脚本是 /usr/bin 目录中的 t 并且你在 /usr 并且你键入 bin/../bin/t 来运行它(是的,这是复杂的),你以 /usr/bin/../bin 结尾作为脚本的目录路径 . 哪个 works ,但......

  • readlink 解决方案on this page,如下所示:

# 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`

readlink isn 't POSIX and apparently the solution relies on GNU' s readlink 其中BSD 's won' t由于某种原因起作用(我无法访问类似BSD的系统进行检查) .

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

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

  • 给了我绝对的道路 .

  • 即使以复杂的方式调用,也要取出时髦的位(参见上面#2的评论) . (例如,至少适度规范化路径 . )

  • 仅依赖于Bash-isms或几乎肯定会出现在最流行的* nix系统(GNU / Linux,BSD和类似BSD的系统,如OS X等)上的东西 .

  • 如果可能,避免调用外部程序(例如,更喜欢Bash内置程序) .

  • Updated ,谢谢你抬起头来,wich)它并不喜欢让它们独自一人,但这并不是必须的 .

23 回答

  • 436

    这里's what I'提出了(编辑:加上sfstewmanlevigrokerKyle StrandRob Kennedy提供的一些调整),这似乎最符合我的"better"标准:

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

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

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

  • 3

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

    您的初始解决方案变为

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

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

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

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

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

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

  • 165

    我今天不得不重新审视这个问题并找到Getting the source directory of a Bash script from within . 它详细阐述了I've used in the past as well的解决方案 .

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

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

  • 5

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

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

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

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

    这是一个选择吗?

  • -1

    获取shell脚本的绝对路径

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

    支持

    • source ./script(由 . 点运算符调用时)

    • 绝对路径/路径/到/脚本

    • 相对路径,如./script

    • /path/dir1/../dir2/dir3/../script

    • 从符号链接调用时

    • 当嵌套符号链接时,例如 foo->dir1/dir2/bar bar->./../doe doe->script

    • 当调用者更改脚本名称时

    I am looking for corner cases where this code does not work . 请告诉我 .

    代码

    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
    

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

  • 8

    使用:

    SCRIPT_PATH=$(dirname `which $0`)
    

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

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

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

  • 1

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

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

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

    在使用之前阅读this answer的评论 .

  • 0

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

    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
    
  • 3

    我们已将自己的产品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 ,它提供了解析物理系统符号链接的功能 . 默认情况下,它保持符号链接不变 .

  • 140

    Bourne shellsh )符合方式:

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

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

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

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

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

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

    有关:

  • 35

    只是:

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

    不需要花哨的操作员 .

  • 31

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

    #!/bin/bash
    currentDir=$(
      cd $(dirname "$0")
      pwd
    )
    
    echo -n "current "
    pwd
    echo script $currentDir
    
  • 24

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

    以下函数(对于bash> = 3.0)为我提供了正确的路径,但是可以调用脚本(直接或通过 source ,具有绝对路径或相对路径):
    ("right path",我的意思是 full absolute path of the script being called ,即使从另一条路径调用,直接或用“ 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]} 来取回实际的脚本 .

  • 5

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

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

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

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

  • 5

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

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

    试试这个:

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

    一个班轮

    `dirname $(realpath $0)`
    
  • 0

    只是为了它的地狱我已经做了一些黑客攻击一个纯文本的东西,纯粹在Bash . 我希望我能 grab 所有边缘案例 .

    请注意,我在另一个答案中提到的 ${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/"
    
  • 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
    
  • 2

    还有另一种方法:

    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
    
  • -4

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

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

相关问题