首页 文章

从内部获取Bash脚本的源目录

提问于
浏览
4151

如何在该脚本中获取Bash脚本所在目录的路径?

例如,假设我想使用Bash脚本作为另一个应用程序的启动器 . 我想将工作目录更改为Bash脚本所在的目录,因此我可以对该目录中的文件进行操作,如下所示:

$ ./application

30 回答

  • 15

    这是特定于Linux的,但您可以使用:

    SELF=$(readlink /proc/$$/fd/255)
    
  • 16

    我厌倦了一遍又一遍地来到这个页面,在接受的答案中复制粘贴单行 . 问题在于它不易理解和记忆 .

    这是一个易于记忆的脚本:

    DIR=$(dirname "${BASH_SOURCE[0]}")  # get the directory name
    DIR=$(realpath "${DIR}")    # resolve its full path if need be
    
  • 12

    我会用这样的东西:

    # retrieve the full pathname of the called script
    scriptPath=$(which $0)
    
    # check whether the path is a link or not
    if [ -L $scriptPath ]; then
    
        # it is a link then retrieve the target path and get the directory name
        sourceDir=$(dirname $(readlink -f $scriptPath))
    
    else
    
        # otherwise just get the directory name of the script path
        sourceDir=$(dirname $scriptPath)
    
    fi
    
  • 52

    这应该这样做:

    DIR=$(dirname "$(readlink -f "$0")")
    

    与路径中的符号链接和空格一起使用 . 请参见 dirnamereadlink 的手册页 .

    编辑:

    从评论轨道看来它似乎不适用于Mac OS . 我不知道为什么会这样 . 有什么建议?

  • 59

    对解决方案进行了轻微修订e-satisf和3bcdnlklvc04a在their answer中指出

    SCRIPT_DIR=''
    pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
        SCRIPT_DIR="$PWD"
        popd > /dev/null
    }
    

    这应该仍然适用于他们列出的所有情况 .

    编辑:由于konsolebox,在推送失败后防止弹出

  • 15

    这些都不适用于Finder在OS X中启动的bash脚本 - 我最终使用:

    SCRIPT_LOC="`ps -p $$ | sed /PID/d | sed s:.*/Network/:/Network/: |
    sed s:.*/Volumes/:/Volumes/:`"
    

    不漂亮,但它完成了工作 .

  • 393
    SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
    
  • 20

    这适用于bash-3.2:

    path="$( dirname "$( which "$0" )" )"
    

    以下是其用法示例:

    假设您有 ~/bin 目录,该目录位于 $PATH 中 . 您在此目录中有脚本 A . 它 source s脚本 ~/bin/lib/B . 您知道包含的脚本相对于原始脚本(子目录 lib )的位置,而不是它相对于用户当前目录的位置 .

    这可以通过以下方法解决(在 A 内):

    source "$( dirname "$( which "$0" )" )/lib/B"
    

    无论用户在哪里或者如何调用脚本都无关紧要,这将始终有效 .

  • 11

    $_值得一提,作为$ 0的替代品 . 如果您从bash运行脚本,则接受的答案可以缩短为:

    DIR="$( dirname "$_" )"
    

    请注意,这必须是脚本中的第一个语句 .

  • 169
    pushd . > /dev/null
    SCRIPT_PATH="${BASH_SOURCE[0]}"
    if ([ -h "${SCRIPT_PATH}" ]); then
      while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`; 
      SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
    fi
    cd `dirname ${SCRIPT_PATH}` > /dev/null
    SCRIPT_PATH=`pwd`;
    popd  > /dev/null
    

    适用于所有版本,包括

    • 通过多深度软链接调用时,

    • 当文件的时候

    • 当脚本通过命令“ source ”又名 . (点)运算符调用时 .

    • 从调用者修改arg $0 时 .

    • "./script"

    • "/full/path/to/script"

    • "/some/path/../../another/path/script"

    • "./some/folder/script"

    或者,如果bash脚本本身是 relative symlink ,您想要关注它并返回链接到脚本的完整路径:

    pushd . > /dev/null
    SCRIPT_PATH="${BASH_SOURCE[0]}";
    if ([ -h "${SCRIPT_PATH}" ]) then
      while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
    fi
    cd `dirname ${SCRIPT_PATH}` > /dev/null
    SCRIPT_PATH=`pwd`;
    popd  > /dev/null
    

    无论如何调用, SCRIPT_PATH 都以完整路径给出 .
    只需确保在脚本开头找到它 .

    此评论和代码Copyleft,GPL2.0或更高版本或CC-SA 3.0(CreativeCommons Share Alike)或更高版本下的可选许可证 . (c)2008年 . 保留所有权利 . 没有任何形式的保证 . 你被警告了 .
    http://www.gnu.org/licenses/gpl-2.0.txt
    http://creativecommons.org/licenses/by-sa/3.0/
    18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656

  • 5

    这将获得Mac OS X 10.6.6上的当前工作目录:

    DIR=$(cd "$(dirname "$0")"; pwd)
    
  • 6

    对于具有GNU coreutils readlink(例如linux)的系统:

    $(readlink -f "$(dirname "$0")")
    

    $0 包含脚本文件名时,无需使用 BASH_SOURCE .

  • 7

    pwd 可用于查找当前工作目录, dirname 可查找特定文件的目录(运行的命令为 $0 ,因此 dirname $0 应该为您提供当前脚本的目录) .

    但是, dirname 精确地给出了文件名的目录部分,它更可能是相对于当前工作目录 . 如果您的脚本由于某种原因需要更改目录,则 dirname 的输出变得毫无意义 .

    我建议如下:

    #!/bin/bash
    
    reldir=`dirname $0`
    cd $reldir
    directory=`pwd`
    
    echo "Directory is $directory"
    

    这样,你得到一个绝对的,而不是相对的目录 .

    由于脚本将在单独的bash实例中运行,因此之后无需恢复工作目录,但如果由于某种原因确实想要更改脚本,则可以轻松地将 pwd 的值分配给变量您更改目录,以备将来使用 .

    虽然只是

    cd `dirname $0`
    

    解决了问题中的具体情况,我发现通常有更多有用的绝对路径 .

  • 8
    #!/bin/sh
    PRG="$0"
    
    # need this for relative symlinks
    while [ -h "$PRG" ] ; do
       PRG=`readlink "$PRG"`
    done
    
    scriptdir=`dirname "$PRG"`
    
  • 10

    简短回答:

    `dirname $0`
    

    或(preferably):

    $(dirname "$0")
    
  • 5508

    您可以使用$ BASH_SOURCE

    #!/bin/bash
    
    scriptdir=`dirname "$BASH_SOURCE"`
    

    请注意,您需要使用#!/ bin / bash而不是#!/ bin / sh,因为它是一个bash扩展名

  • 14

    我尝试了其中的每一个,但没有一个有效 . 一个非常接近,但有一个小虫子,打破了它;他们忘了用引号包裹路径 .

    还有很多人认为你是从shell运行脚本所以忘记当你打开一个新的脚本它默认你的家 .

    试试这个目录的大小:

    / var /没有人/思想/关于空间存在/在目录/名称/这里是你的file.text

    无论您运行的方式和位置如何,这都是正确的 .

    #!/bin/bash
    echo "pwd: `pwd`"
    echo "\$0: $0"
    echo "basename: `basename "$0"`"
    echo "dirname: `dirname "$0"`"
    

    因此,为了使它真正有用,这里是如何更改到正在运行的脚本的目录:

    cd "`dirname "$0"`"
    

    希望有所帮助

  • 95

    尝试使用:

    real=$(realpath $(dirname $0))
    
  • 5

    dirname命令是最基本的,只需解析直到$ 0(脚本名称)变量的文件名的路径:

    dirname "$0"
    

    但是,正如 matt b 指出的那样,返回的路径因脚本的调用方式而异 . pwd没有't do the job because that only tells you what the current directory is, not what directory the script resides in. Additionally, if a symbolic link to a script is executed, you'将获得链接所在位置(可能是相对的),而不是实际的脚本 .

    其他一些人提到了 readlink 命令,但最简单的是,您可以使用:

    dirname "$(readlink -f "$0")"
    

    readlink将脚本路径解析为文件系统根目录的绝对路径 . 因此,任何包含单点或双点,波浪线和/或符号链接的路径都将被解析为完整路径 .

    这是一个演示这些内容的脚本, whatdir.sh:

    #!/bin/bash
    echo "pwd: `pwd`"
    echo "\$0: $0"
    echo "basename: `basename $0`"
    echo "dirname: `dirname $0`"
    echo "dirname/readlink: $(dirname $(readlink -f $0))"
    

    使用相对路径在我的主目录中运行此脚本:

    >>>$ ./whatdir.sh 
    pwd: /Users/phatblat
    $0: ./whatdir.sh
    basename: whatdir.sh
    dirname: .
    dirname/readlink: /Users/phatblat
    

    同样,但使用脚本的完整路径:

    >>>$ /Users/phatblat/whatdir.sh 
    pwd: /Users/phatblat
    $0: /Users/phatblat/whatdir.sh
    basename: whatdir.sh
    dirname: /Users/phatblat
    dirname/readlink: /Users/phatblat
    

    现在更改目录:

    >>>$ cd /tmp
    >>>$ ~/whatdir.sh 
    pwd: /tmp
    $0: /Users/phatblat/whatdir.sh
    basename: whatdir.sh
    dirname: /Users/phatblat
    dirname/readlink: /Users/phatblat
    

    最后使用符号链接来执行脚本:

    >>>$ ln -s ~/whatdir.sh whatdirlink.sh
    >>>$ ./whatdirlink.sh 
    pwd: /tmp
    $0: ./whatdirlink.sh
    basename: whatdirlink.sh
    dirname: .
    dirname/readlink: /Users/phatblat
    
  • 31

    这是符合POSIX标准的单线程:

    SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`
    
    # test
    echo $SCRIPT_PATH
    
  • 26

    尝试以下交叉兼容的解决方案:

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

    因为 realpathreadlink 命令并不总是可用(取决于操作系统), ${BASH_SOURCE[0]} 仅在bash shell中可用 .

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

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

    此函数需要1个参数 . 如果参数已经是绝对路径,则按原样打印,否则打印 $PWD variable filename参数(不带 ./ 前缀) .

    有关:

  • 7

    我认为这并不像其他人那样容易实现 . pwd不起作用,因为当前目录不一定是脚本的目录 . $ 0并不总是有信息 . 考虑以下三种调用脚本的方法 .

    ./script
    
    /usr/bin/script
    
    script
    

    在第一种和第三种方式中,$ 0没有完整的路径信息 . 在第二和第三,pwd不起作用 . 以第三种方式获取目录的唯一方法是遍历路径并找到具有正确匹配的文件 . 基本上代码必须重做操作系统的功能 .

    执行所要求的一种方法是仅对/ usr / share目录中的数据进行硬编码,并通过完整路径引用它 . 无论如何数据都不在/ usr / bin目录中,所以这可能是要做的事情 .

  • 6

    使用 dirname "$0"

    #!/bin/bash
    echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
    echo "The present working directory is `pwd`"
    

    如果您没有从包含它的目录运行脚本,则单独使用 pwd 将不起作用 .

    [matt@server1 ~]$ pwd
    /home/matt
    [matt@server1 ~]$ ./test2.sh
    The script you are running has basename test2.sh, dirname .
    The present working directory is /home/matt
    [matt@server1 ~]$ cd /tmp
    [matt@server1 tmp]$ ~/test2.sh
    The script you are running has basename test2.sh, dirname /home/matt
    The present working directory is /tmp
    
  • 704

    这是一个简单,正确的方法:

    actual_path=$(readlink -f "${BASH_SOURCE[0]}")
    script_dir=$(dirname "$actual_path")
    

    说明:

    • ${BASH_SOURCE[0]} - 脚本的完整路径 . 即使在获取脚本时,这个值也是正确的,例如 source <(echo 'echo $0') 打印 bash ,而用 ${BASH_SOURCE[0]} 替换它将打印脚本的完整路径 . (当然,这假设您可以依赖Bash . )

    • readlink -f - 以递归方式解析指定路径中的所有符号链接 . 这是GNU扩展,在(例如)BSD系统上不可用 . 如果你正在运行Mac,你可以使用Homebrew来安装GNU coreutils 并用 greadlink -f 取代它 .

    • 当然 dirname 获取路径的父目录 .

  • 92

    我已经比较了给出的许多答案,并提出了一些更紧凑的解决方案 . 这些似乎可以处理由您最喜欢的组合产生的所有疯狂边缘情况:

    • 绝对路径或相对路径

    • 文件和目录软链接

    • 调用为 scriptbash scriptbash -c scriptsource script. script

    • 目录和/或文件名中的空格,制表符,换行符,unicode等

    • 以连字符开头的文件名

    如果您从Linux运行,似乎使用 proc 句柄是找到当前正在运行的脚本的完全解析源的最佳解决方案(在交互式会话中,链接指向相应的 /dev/pts/X ):

    resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"
    

    这有一点点丑陋,但修复紧凑,易于理解 . 我们对此没有好处,因为 readlink 大大简化了任务 . echo XX 添加到变量字符串的末尾,以便文件名中的任何尾随空格都不会被吃掉,并且该行末尾的参数替换 ${VAR%X} 将删除 X . 因为 readlink 添加了一个自己的换行符(如果不是我们之前的诡计,通常会在命令替换中被吃掉),我们也必须摆脱它 . 这是使用 $'' 引用方案最容易实现的,它允许我们使用转义序列(如 \n )来表示换行符(这也是您可以轻松地制作狡猾的命名目录和文件) .

    以上内容应该包括您在Linux上查找当前运行的脚本的需求,但如果您没有 proc 文件系统,或者're trying to locate the fully resolved path of some other file, then maybe you'll会发现以下代码有用 . 它's only a slight modification from the above one-liner. If you'重新使用奇怪的目录/文件名,用 lsreadlink 检查输出是有用的,因为 ls 将输出"simplified"路径,用 ? 代替换行符 .

    absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
    dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
    file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}
    
    ls -l -- "$dir/$file"
    printf '$absolute_path: "%s"\n' "$absolute_path"
    
  • 32

    所以...我相信我有这个 . 晚到了派对,但我觉得有些人会欣赏它在这里是他们来的穿过这个线程 . 评论应该解释 .

    #!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.
    
    ## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
    ## dereference symbolic links (ala 'readlink') until the originating file
    ## is found. This is effectively the same function provided in stdlib.h as
    ## 'realpath' and on the command line in GNU 'readlink -f'.
    
    ## Neither of these tools, however, are particularly accessible on the many
    ## systems that do not have the GNU implementation of readlink, nor ship
    ## with a system compiler (not to mention the requisite knowledge of C).
    
    ## This script is written with portability and (to the extent possible, speed)
    ## in mind, hence the use of printf for echo and case statements where they
    ## can be substituded for test, though I've had to scale back a bit on that.
    
    ## It is (to the best of my knowledge) written in standard POSIX shell, and
    ## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
    ## issues with it, though I'm not sure why; so probably best to avoid for now.
    
    ## Particularly useful (in fact, the reason I wrote this) is the fact that
    ## it can be used within a shell script to find the path of the script itself.
    ## (I am sure the shell knows this already; but most likely for the sake of
    ## security it is not made readily available. The implementation of "$0"
    ## specificies that the $0 must be the location of **last** symbolic link in
    ## a chain, or wherever it resides in the path.) This can be used for some
    ## ...interesting things, like self-duplicating and self-modifiying scripts.
    
    ## Currently supported are three errors: whether the file specified exists
    ## (ala ENOENT), whether its target exists/is accessible; and the special
    ## case of when a sybolic link references itself "foo -> foo": a common error
    ## for beginners, since 'ln' does not produce an error if the order of link
    ## and target are reversed on the command line. (See POSIX signal ELOOP.)
    
    ## It would probably be rather simple to write to use this as a basis for
    ## a pure shell implementation of the 'symlinks' util included with Linux.
    
    ## As an aside, the amount of code below **completely** belies the amount
    ## effort it took to get this right -- but I guess that's coding for you.
    
    ##===-------------------------------------------------------------------===##
    
    for argv; do :; done # Last parameter on command line, for options parsing.
    
    ## Error messages. Use functions so that we can sub in when the error occurs.
    
    recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
    dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
    errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.
    
    # Probably best not to install as 'pathfull', if you can avoid it.
    
    pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"
    
    ## 'test and 'ls' report different status for bad symlinks, so we use this.
    
     if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
        errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
        recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
        dangling 1>&2; exit 1; fi
     fi
    
    ## Not a link, but there might be one in the path, so 'cd' and 'pwd'.
    
     if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
       printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
     fi
    
    ## Walk the symlinks back to the origin. Calls itself recursivly as needed.
    
     while [ "$link" ]; do
       cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
       case "$newlink" in
        "$link") dangling 1>&2 && exit 1                                       ;;
             '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
              *) link="$newlink" && pathfull "$link"                           ;;
       esac
     done
     printf "$(pwd)/$(basename "$newlink")\n"
    }
    
    ## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
    ## else, symlink again (maybe with a different name) elsewhere, and link
    ## back into the directory you started in (or something.) The absolute path
    ## of the script will always be reported in the usage, along with "$0".
    
    if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"
    
    # Yay ANSI l33t codes! Fancy.
     printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
     printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
     printf "Recursive readlink for the authoritative file, symlink after "
     printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
     printf " From within an invocation of a script, locate the script's "
     printf "own file\n         (no matter where it has been linked or "
     printf "from where it is being called).\n\n"
    
    else pathfull "$@"
    fi
    
  • 24
    #!/bin/bash
    
    DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
    

    是一个有用的单行程序,它将为您提供脚本的完整目录名称,无论它在何处被调用 .

    只要用于查找脚本的路径的最后一个组件不是符号链接(目录链接正常),它就会起作用 . 如果您还想解决脚本本身的任何链接,您需要一个多线解决方案:

    #!/bin/bash
    
    SOURCE="${BASH_SOURCE[0]}"
    while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
      DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null && pwd )"
      SOURCE="$(readlink "$SOURCE")"
      [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
    done
    DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null && pwd )"
    

    最后一个可以使用别名的任意组合, sourcebash -c ,符号链接等 .

    注意:如果您在运行此代码段之前 cd 到另一个目录,结果可能不正确!另外,请注意$CDPATH gotchas .

    要了解它是如何工作的,请尝试运行这个更详细的表单:

    #!/bin/bash
    
    SOURCE="${BASH_SOURCE[0]}"
    while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
      TARGET="$(readlink "$SOURCE")"
      if [[ $TARGET == /* ]]; then
        echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
        SOURCE="$TARGET"
      else
        DIR="$( dirname "$SOURCE" )"
        echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
        SOURCE="$DIR/$TARGET" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
      fi
    done
    echo "SOURCE is '$SOURCE'"
    RDIR="$( dirname "$SOURCE" )"
    DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
    if [ "$DIR" != "$RDIR" ]; then
      echo "DIR '$RDIR' resolves to '$DIR'"
    fi
    echo "DIR is '$DIR'"
    

    它将打印如下:

    SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
    SOURCE is './sym2/scriptdir.sh'
    DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
    DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
    
  • 11

    嗯,如果在路径basename&dirname中只是不打算切断它并且走路很难(如果父节点没有导出PATH怎么办!) . 但是,shell必须有一个打开它的脚本句柄,而在bash中句柄是#255 .

    SELF=`readlink /proc/$$/fd/255`
    

    适合我 .

  • 24
    $(dirname "$(readlink -f "$BASH_SOURCE")")
    
  • 8

    我认为最好的紧凑型解决方案是:

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

    除了Bash之外,没有任何依赖 . 使用 dirnamereadlinkbasename 最终会导致兼容性问题,因此如果可能的话,最好避免使用它们 .

相关问题