写脚本时经常需要引用脚本同目录下的其他文件(配置、日志目录、依赖脚本等)。这时很多人的第一反应是用 pwd 或者 $0,但这两种做法都有陷阱。总结一下,最可靠的做法其实就是一条命令:
root_path=$(cd $(dirname ${BASH_SOURCE[0]}); pwd)
为什么不能用 pwd
pwd 返回的是当前工作目录,也就是你执行脚本时所在的目录,而不是脚本文件所在的目录。比如:
$ pwd
/home/user/project
$ /opt/tools/myapp/run.sh
此时脚本内部执行 pwd 得到的是 /home/user/project,而脚本实际位于 /opt/tools/myapp/。如果脚本里用 pwd 拼路径去读配置,只要换一个目录启动脚本就会出错。
为什么不能直接用 $0
$0 是脚本被调用时的名字,它有几个问题:
- 如果用相对路径执行(如
cd /opt/tools; ./myapp/run.sh),$0只是./myapp/run.sh这样的相对路径,不是绝对路径; - 如果脚本被
source进来,$0保留的是外层 Shell 的名字(比如-bash),完全不是脚本自己的路径; - 在被其他脚本调用等场景下,
$0的值也不可靠。
BASH_SOURCE[0] 则始终指向”当前正在执行的这段代码所在的脚本文件”,即使脚本被 source,也能拿到正确的路径,因此比 $0 更稳。
命令逐部分拆解
root_path=$(cd $(dirname ${BASH_SOURCE[0]}); pwd)
${BASH_SOURCE[0]}:当前脚本自身的文件名(相对于调用时的路径,可能是绝对路径、相对路径或裸文件名)。$(dirname ...):取路径中的目录部分。比如${BASH_SOURCE[0]}是./tools/run.sh,dirname之后得到./tools;如果只是run.sh,则得到.。cd ... ; pwd:先cd进这个目录(无论相对路径是什么形状,cd之后pwd一定返回规范的绝对路径),再输出当前绝对路径。$( )(最外层):命令替换,把子 Shell 中pwd的输出赋值给变量root_path。
整个过程发生在一个子 Shell 中,所以中途的 cd 不会影响脚本当前的工作目录,这也是这条命令写法上的精妙之处。
使用示例
#!/bin/bash
root_path=$(cd $(dirname ${BASH_SOURCE[0]}); pwd)
echo "脚本所在目录:$root_path"
# 加载同目录下的配置
source "$root_path/config.env"
无论从哪个目录、用绝对路径还是相对路径执行这个脚本,root_path 都会正确指向脚本文件所在的目录。
小结
pwd是”人在哪里”,不是”脚本在哪里”;$0取决于调用方式,source时会失真;$(cd $(dirname ${BASH_SOURCE[0]}); pwd)通过子 Shell 先跳转再取址,稳定可靠,可以直接背下来当模板用。