如何在Bash中将当前工作目录设置为脚本所在的目录?

785

我正在编写一个Bash脚本,需要让当前工作目录始终是脚本所在的目录。

默认行为是脚本的当前工作目录与运行脚本的shell一样,但我不想要这种行为。


你是否考虑过在 /usr/bin 这样的地方放置一个包装脚本,以便进入(硬编码的)正确目录并执行你的脚本? - Dagg Nabbit
2
你为什么需要脚本的目录?解决潜在问题可能有更好的方法。 - bstpierre
18
我想指出的是,你所称之为“明显不良”的行为实际上是完全必要的——如果我运行 myscript path/to/file ,我希望该脚本相对于我的当前目录来评估 path/to/file ,而不是脚本所在的任何目录。此外,如果使用 ssh remotehost bash < ./myscript 运行脚本,会发生什么情况,就像 BASH FAQ 中提到的那样? - Gordon Davisson
可能是Can a Bash script tell what directory it's stored in?的重复问题。 - kenorb
4
cd "${BASH_SOURCE%/*}" || exit的翻译是:进入"${BASH_SOURCE%/*}"目录,如果失败则退出。 - caw
13个回答

934
#!/bin/bash
cd "$(dirname "$0")"

9
在使用 Windows 的 bash 时,dirname 返回 '.'。因此,Paul 的答案更好。 - Tvaroh
9
在Mac OSX中也会返回'.'。 - Ben Clayton
4
值得注意的是,如果符号链接构成$0的一部分,则可能会导致某些事物出现问题。例如,在您的脚本中,您可能期望../../引用位于脚本位置上面两级的目录,但如果涉及符号链接,则不一定如此。 - Rag
36
如果您将脚本命名为./script,那么.就是正确的目录。切换到.也会最终进入脚本所在的那个目录,也就是当前工作目录。 - ndim
12
如果您像这样从当前目录运行脚本 bash script.sh,那么 $0 的值就是 script.sh。唯一能让 cd 命令“工作”的方式是因为您不关心失败的命令。如果您使用 set -o errexit(又称:set -e)来确保您的脚本不会跳过失败的命令,那么这样将无法工作,因为 cd script.sh 是一个错误。可靠的 [bash 特定] 方法是 cd "$(dirname ${BASH_SOURCE[0]})" - Bruno Bronosky
显示剩余14条评论

392
以下内容同样适用:
cd "${0%/*}"

关于语法的详细描述可在这篇 StackOverflow 答案中找到。


21
解释它的工作原理:https://dev59.com/7Ww15IYBdhLWcg3wy-zO - kenorb
29
如果路径中包含空格,请不要忘记用引号将其括起来。例如,cd "${0%/*}"。 - H.Rabiee
23
如果使用 bash script.sh 来执行脚本,这个方法会失败—— $0 只会是文件的名称。 - Chris Watts
2
无论脚本是否在根目录中,都不起作用,因为“$ {0%/ *}”会扩展为空字符串。 - jmster
21
我认为这个答案过于简洁。你几乎无法了解有关语法的任何信息。一些关于如何阅读它的描述会很有帮助。 - fraxture
显示剩余7条评论

252

尝试以下简单的一行命令:


适用于所有 UNIX/OSX/Linux:

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

Bash

dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

注意:在命令中双破折线(--)表示命令选项的结束,因此包含破折号或其他特殊字符的文件不会破坏命令。

注意:在Bash中,使用${BASH_SOURCE[0]}代替$0,否则在源代码时路径可能会出现问题(source/.)。


*适用于Linux、Mac和其他BSD:

cd "$(dirname "$(realpath -- "$0")")";

注意:默认情况下(如Ubuntu等),在大多数流行的Linux发行版中应该已经安装了realpath,但在某些发行版中可能会缺少,因此您需要手动安装。

注意:如果您使用Bash,请使用${BASH_SOURCE[0]}代替$0,否则在引用(source/.)时路径可能会出错。

否则,您可以尝试类似以下的操作(它将使用第一个存在的工具):

cd "$(dirname "$(readlink -f -- "$0" || realpath -- "$0")")"

针对Linux系统:

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

*在BSD/Mac上使用GNU readlink:

cd "$(dirname "$(greadlink -f -- "$0")")"

注意:你需要安装 coreutils(例如,1. 安装Homebrew,2. brew install coreutils)。


在bash中

在bash中,你可以使用参数展开来实现这个目的,例如:

cd "${0%/*}"

但是如果从同一目录运行脚本,则无法工作。

或者您可以在bash中定义以下函数:

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

该函数需要一个参数。如果参数已经是绝对路径,则原样打印它,否则打印$PWD变量和文件名参数(不包括./前缀)。

或者这里是来自Debian .bashrc文件的版本:

function realpath()
{
    f=$@
    if [ -d "$f" ]; then
        base=""
        dir="$f"
    else
        base="/$(basename -- "$f")"
        dir="$(dirname -- "$f")"
    fi
    dir="$(cd -- "$dir" && /bin/pwd)"
    echo "$dir$base"
}

相关:

另请参阅:

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


7
比流行的答案更好,因为它解决了系统链接问题,并考虑了不同的操作系统。谢谢! - Dennis
3
在命令中,双破折号(--)用于表示命令选项的结束,这样包含破折号或其他特殊字符的文件就不会中断命令。例如尝试通过 touch "-test"touch -- -test 创建文件,然后通过 rm "-test"rm -- -test 删除该文件,看看它们之间的区别。 - kenorb
1
如果可以的话,我会取消我的点赞:realpath已被弃用。两者之间有什么区别:realpath和readlink-f? - lsh
2
@lsh 它说只有 Debian 的 realpath 包已被弃用,而不是 GNU 的 realpath。如果您认为不清楚,可以建议进行编辑。 - kenorb
2
所有像 cd "$(dirname "$(realpath "$0")")" 这样的例子,是否也应该在参数前使用 --,像这样 cd "$(dirname -- "$(realpath -- "$0")")" - ntninja
显示剩余7条评论

96
cd "$(dirname "${BASH_SOURCE[0]}")"

很容易,它有效。


1
这应该是被接受的答案。适用于OSX和Linux Ubuntu。 - David Rissato Cruz
6
当脚本B被从另一个脚本A调用时,这很有效,并且您需要使用相对于脚本B的路径。谢谢。 - Enrico
7
对于我们这些不得不碰 Windows 的人来说,Cygwin 也可以使用这种方法。 - Bruno Bronosky
4
或者 cd "${BASH_SOURCE%/*}" || exit - caw
2
适用于macOS Mojave。 - Juan Boero

20

对于未被符号链接到其他位置(例如$PATH中)的脚本,接受的答案效果很好。

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

然而,如果脚本通过符号链接运行,

ln -sv ~/project/script.sh ~/bin/; 
~/bin/script.sh

这将会进入~/bin/目录而不是~/project/目录,如果cd的目的是相对于~/project/包含依赖项,那么这可能会破坏您的脚本。

符号链接安全答案如下:

#!/bin/bash
cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"  # cd current directory

readlink -f 命令用于解析可能是符号链接的文件的绝对路径。

引号必需用于支持可能包含空格的文件路径(不好的做法,但是不能保证这种情况不存在)。


15

这里有很多正确的答案,但对我来说更有用的是使用pushd/popd来确保脚本的相对路径始终可预测/可用:

pushd "$(dirname ${BASH_SOURCE:0})"
trap popd EXIT

# ./xyz, etc...

这将把源文件所在的目录推入导航栈中,从而更改工作目录,但是当脚本退出(包括失败)时,trap 将运行 popd,恢复执行之前的当前工作目录。如果脚本进行了 cd 操作并失败,则执行结束后终端可能处于不可预测的状态 - trap 可以防止这种情况发生。


9
这个脚本似乎对我有效:
#!/bin/bash
mypath=`realpath $0`
cd `dirname $mypath`
pwd

pwd命令行会在无论我从哪里运行它,都会将脚本所在的位置作为当前工作目录输出。


3
realpath 不太可能被安装在所有地方。这可能并不重要,具体取决于提问者的情况。 - bstpierre
我的系统没有 realpath,但是它有 readlink,看起来很相似。 - Dennis Williamson
2
哪个发行版默认没有安装 realpath?Ubuntu 默认安装了它。 - kenorb

3
我用这个,很有效。
#!/bin/bash
cd "$(dirname "$0")"
CUR_DIR=$(pwd)

2

获取脚本的真实路径

if [ -L $0 ] ; then
    ME=$(readlink $0)
else
    ME=$0
fi
DIR=$(dirname $ME)

(这是对我在此处提出的相同问题的答案:获取脚本执行所在目录的名称


1
cd "`dirname $(readlink -f ${0})`"

1
你能解释一下你的答案吗? - Zulu
2
尽管这段代码可能有助于解决问题,但它并没有解释为什么以及如何回答这个问题。提供这种额外的上下文将显著提高其长期教育价值。请编辑您的答案以添加说明,包括适用的限制和假设。 - Toby Speight
${0}将给出脚本的文件名。 readlink -f {0}将给出该脚本的绝对路径。 dirname将从先前结果中提取所在路径的脚本。 - purushothaman poovai

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