bash脚本里如何得到脚本文件所在路径
bash脚本里如何得到脚本文件所在路径
应用背景
我们在脚本里面会经常调用外部程序,如何指定外部程序路径:
- 绝对路径写死;缺点是不方便移植,比如更改安装路径等。
- 加到PATH搜索目录下面;缺点是PATH路径会被替换,同名程序的存在会导致调用到另一个程序。
而有时,我们希望被调用的外部程序是相对当前脚本文件的,例如和脚本文件在同一目录下面,或者临近相对的子目录等等;
|-- ROOT
|-- mains.h
| |-- scripts
| | |-- sub1.sh
| | |-- sub2.sh
| |-- tools
| | |-- tool1
| | |-- tool2
例如main.sh是主脚本,它需要调用scripts/sub.sh,tools/tool1,和tools/tool2,此时如果把所有的路径(ROOT:ROOT/scripts:ROOT/tools)都加到PATH里面显得有点重,那么我们只想把ROOT加到PATH里面,然后让main.sh来去搜索sub.sh和tool。
思路
这种场景下,我们就需要在main.sh里面得到脚本自己所在的路径(例如,SCRIPTDIR),然后按如下方式调用子程序:
${SCRIPTDIR}/scripts/sub.sh
${SCRIPTDIR}/tools/tool
方法
如何得到main.sh脚本所在的路径呢,下述命令可以得到:
SCRIPTDIR=$(cd $(dirname "${BASH_SOURCE[0]}") >/dev/null && pwd)
- 例子1:
假设脚本main.sh所在的路径是/home/<username>/main.sh
$ cat main.sh
#!/bin/bash
SCRIPTDIR=$(cd $(dirname "${BASH_SOURCE[0]}") >/dev/null && pwd)
echo ${SCRIPTDIR}
$ pwd
/home/<username>/tmp
$ ./main.sh
/home/<username>/tmp
$ cd ..
$ pwd
/home/<username>
$ ./tmp/main.sh
/home/<username>/tmp
我们可以看到,不管当前的路径在哪里,调用起main.sh的时候它都能打印出脚本所在的路径。
再看一个例子。
- 例子2:
$ pwd
/home/<username>
$ ln -snf tmp/main.sh main.sh
$ ./main.sh
/home/<username>
我们创建一个符号链接,main.sh链接到tmp/main.sh,然后我们运行外层的main.sh这个符号链接,结果我们看到:打印出来的是符号链接文件所在的路径,而不是脚本所在的路径。
这是合理的设计行为,因为我们调用的是外面的main.sh这个链接文件,但是对我们的需求就有问题了,因为在main.sh这个真正脚本里面就没法调用到子命令sub.sh和tool了,无法根据外面符号链接的路径来找scripts和tools路径了。
解决办法呢,我们要不停的解决符号链接行为,直到遇到真正的脚本为止。
重新改写tmp/main.sh脚本如下:
#!/bin/bash
#SCRIPTDIR=$(cd $(dirname "${BASH_SOURCE[0]}") >/dev/null && pwd)
SOURCE="${BASH_SOURCE[0]}"
while [ -h "${SOURCE}" ]; do
SCRIPTDIR="$(cd -P "$(dirname "${SOURCE}")" >/dev/null && pwd)"
SOURCE="$(readlink "${SOURCE}")"
[[ ${SOURCE} != /* ]] && SOURCE="${SCRIPTDIR}/${SOURCE}"
done
SCRIPTDIR="$(cd -P "$(dirname "${SOURCE}")" >/dev/null && pwd)"
echo ${SCRIPTDIR}
再次运行符号链接文件:
$ pwd
/home/<username>
$ ln -snf tmp/main.sh main.sh
$ ./main.sh
/home/<username>/tmp
这回打印出来的就是真正的脚本文件所在的路径了。