Unix shell脚本如何查找脚本文件所在的目录?

676

基本上我需要使用与shell脚本文件位置相关的路径来运行脚本,如何将当前目录更改为与脚本文件所在目录相同?

您可以使用以下命令将当前工作目录更改为与脚本文件所在目录相同:
``` cd "$(dirname "$0")" ```
其中 `$0` 是脚本文件的名称,`$(dirname "$0")` 获取该文件所在的目录,并使用 `cd` 命令将当前工作目录切换到那个目录。

15
这真的是一个重复的问题吗?这个问题是关于“Unix shell脚本”的,而另一个问题则特别涉及Bash。 - michaeljt
3
@BoltClock:这个问题被错误地关闭了。链接的问题是关于Bash的。而这个问题是关于Unix shell编程的。请注意,被接受的答案是非常不同的! - Dietrich Epp
3
我认为这个答案更好:从Bash脚本内获取源目录 - Alan Zhiliang Feng
2
可能是从内部获取Bash脚本的源目录的重复问题。 - dulgan
1
谷歌把我带到了这里,因为我正在寻找一个纯粹的 POSIX 实现方案。经过更多的搜索,我发现了这个非常详细的答案,解释了为什么它不能被实现,并通过支持流行 shell 的 shimming 方法来解决它。https://dev59.com/fl0a5IYBdhLWcg3w48JP#29835459 - Steve Buzonas
显示剩余2条评论
19个回答

742
在Bash中,你应该像这样获取你所需要的内容:
#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

28
如果您通过符号链接在不同目录中调用脚本,则此方法无效。 要使其工作,您还需要使用readlink命令(请参见下面al的答案)。 - AndrewR
59
在Bash中,使用$BASH_SOURCE代替$0更安全,因为$0并不总是包含正在调用的脚本的路径,比如在"source"脚本时。 - mklement0
7
$BASH_SOURCE 是 Bash 特有的,这个问题是关于一般的 shell 脚本。 - Ha-Duong Nguyen
11
@auraham:CUR_PATH=$(pwd)pwd 命令返回当前目录(并不一定是脚本的父目录)! - Andreas Covidiot
5
我试过了@mklement0推荐的方法,使用$BASH_SOURCE,它返回了我需要的结果。我的脚本被另一个脚本调用,而$0返回.,而$BASH_SOURCE返回了正确的子目录(在我的情况下是scripts)。 - David Rissato Cruz
显示剩余12条评论

504
原帖中包含解决方案(忽略回复,它们没有添加任何有用的内容)。感兴趣的工作由提到的 Unix 命令 readlink 选项 -f 完成。当脚本被绝对路径和相对路径调用时都可以使用。
对于 bash、sh、ksh:
#!/bin/bash 
# Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")
# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
echo $SCRIPTPATH

对于tcsh、csh:

#!/bin/tcsh
# Absolute path to this script, e.g. /home/user/bin/foo.csh
set SCRIPT=`readlink -f "$0"`
# Absolute path this script is in, thus /home/user/bin
set SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH

另请参阅:https://dev59.com/yXVD5IYBdhLWcg3wL4cA#246128


14
注意:并非所有系统都有readlink命令。这就是我建议使用bash内置的pushd/popd命令的原因。 - docwhat
29
readlink-f 选项在 OS X(Lion)和可能的 BSD 上有不同的功能。 https://dev59.com/UnNA5IYBdhLWcg3wL62x - Ergwun
12
为了澄清@Ergwun的评论:在OS X(Lion版本)中根本不支持-f;你可以删除-f,以解决至多一个间接级别的解析,例如pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")",或者按照链接帖子中演示的方式编写自己的递归符号链接跟随脚本。 - mklement0
2
我仍然不明白为什么OP需要绝对路径。如果您想相对于脚本路径访问文件并像./myscript.sh这样调用脚本,则报告“。”应该可以正常工作。 - Stefan Haberl
13
如果您在运行脚本时当前工作目录与脚本位置不同(例如 sh /some/other/directory/script.sh),那么这可能会成为一个问题。在这种情况下,. 将是您的当前工作目录,而不是 /some/other/directory。请注意调整当前工作目录以匹配脚本位置。 - Jon z
显示剩余5条评论

71

之前在一个回答中已经提到了这个问题,但是在所有其他回答中很容易被忽略。

在使用bash时:

echo this file: "$BASH_SOURCE"
echo this dir: "$(dirname "$BASH_SOURCE")"

Bash参考手册,5.2 Bash变量


3
只有这个在环境路径中可以运行脚本,最受欢迎的那些都无法运行。谢谢! - July
您应该使用dirname "$BASH_SOURCE"来处理$BASH_SOURCE中的空格。 - Mygod
1
根据工具ShellCheck的建议,更明确打印目录的方法是:"$(dirname "${BASH_SOURCE{0}}")",因为BASH_SOURCE是一个数组,在没有下标的情况下,默认会取第一个元素。 - Adrian M.
1
@AdrianM.,你需要使用括号而不是大括号来索引:"$(dirname "${BASH_SOURCE[0]}")"。 - Hashbrown
1
对我没用,只打印一个点(即当前目录,可能是默认值)。 - JL_SO

55

假设您正在使用bash

#!/bin/bash

current_dir=$(pwd)
script_dir=$(dirname "$0")

echo $current_dir
echo $script_dir

这个脚本应该打印你所在的目录,然后是脚本所在的目录。例如,在/中调用脚本并将其放在/home/mez/中时,它输出:

/
/home/mez

记住,当从命令输出中分配变量时,请用 $(括起该命令 - 否则您将无法获得所需的输出。


4
如果我从当前目录调用这个脚本,它就不会起作用。请帮我翻译这句话。 - Eric
7
@EricWang,你始终处于当前目录。 - ctrl-alt-delor
1
对我来说,$current_dir确实是我调用脚本的路径。然而,$script_dir不是脚本的目录,它只是一个点。 - Michael
1
@Michael,script_dir相对于current_dir。因此,如果您从存储脚本的目录运行脚本,则在script_dir中只会得到一个点。 - Vaibhav Jain
$PWD相比,$(pwd)的开销要大得多,而POSIX明确要求在shell启动和cd时设置它。 - Charles Duffy

46

这个问题的最佳答案已经在这里回答了:
从Bash脚本内获取源目录

答案是:

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

无论脚本从哪里调用,都可以使用一行命令获取完整的脚本目录。

为了理解其工作原理,您可以执行以下脚本:

#!/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'"

35
如果您正在使用bash……
#!/bin/bash

pushd $(dirname "${0}") > /dev/null
basedir=$(pwd -L)
# Use "pwd -P" for the path without links. man bash for more info.
popd > /dev/null

echo "${basedir}"

4
еҰӮжһңе…¶д»–shellж”ҜжҢҒpwd -LпјҢдҪ еҸҜд»Ҙз”Ёcd $(dirname "${0}") е’Ң cd -д»Јжӣҝpushd/popdе‘Ҫд»ӨпјҢд»ҘдҪҝе‘Ҫд»ӨеңЁе…¶д»–shellдёҠд№ҹиғҪжӯЈеёёдҪҝз”ЁгҖӮиҜ·жіЁж„ҸдёҚиҰҒж”№еҸҳеҺҹж„ҸгҖӮ - docwhat
在这里,你为什么要使用 pushd 和 popd? - qodeninja
1
所以我不必将原始目录存储在变量中。这是我在函数等中经常使用的模式。它嵌套得非常好,这很好。 - docwhat
无论变量是否在脚本中被引用,它仍然存储在内存中的变量中。此外,我认为执行pushd和popd的成本远远超过在脚本中不创建本地Bash变量所节省的成本,无论是在CPU周期还是可读性方面。 - ingyhere

26

如果您想获取实际的脚本目录(无论您是使用符号链接还是直接调用脚本),请尝试:

BASEDIR=$(dirname $(realpath "$0"))
echo "$BASEDIR"

这适用于linux和macOS。我没有看到任何人提到realpath。不确定这种方法是否有任何缺点。

在macOS上,您需要安装coreutils才能使用realpath。例如:brew install coreutils


这似乎在符合POSIX标准的shell(如ashdash,例如在Alpine Linux上)上运行良好。 - jchook

23

正如Marko所建议的:

BASEDIR=$(dirname $0)
echo $BASEDIR

除非您从脚本所在的相同目录执行脚本,否则此方法有效;在这种情况下,您将得到一个'.'的值。

为了解决这个问题,请使用:

current_dir=$(pwd)
script_dir=$(dirname $0)

if [ $script_dir = '.' ]
then
script_dir="$current_dir"
fi

现在你可以在整个脚本中使用变量 current_dir 来引用脚本目录。但是这可能仍然存在符号链接问题。


21

1
如果路径中有空格,则会返回“cd:参数过多”的错误信息,并返回“$PWD”(这是一个显而易见的修复方法,但也展示了实际上存在多少边缘情况)。 - michael
1
会点赞。除了@michael的评论指出它在路径中有空格时无法运行...是否有修复方法? - spechter
2
ln -s /home/der/1/test /home/der/2/test && /home/der/2/test => /home/der/2(应该显示原始脚本的路径) - Der_Meister
1
太棒了!这是唯一与 #!/bin/sh 兼容的解决方案!我的好男人。 - s3c
1
@JánSáreník 我认为你想使用的应该是 BINDIR=$(cd "$a"; pwd) 而不是 BINDIR="$(cd $a; pwd)",因为当 $a 中有空格时,cd $a 会失败,而 $(...) 本身就被视为带引号的,因此在其周围添加额外的引号 - 即:"$(...)" 是多余的。 - michael
显示剩余3条评论

20
cd $(dirname $(readlink -f $0))

感谢来自未来12年的支持!简单明了。 - Mendhak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接