已开通新的博客,后续文字都会发到新博客
http://www.0xfree.top
我们在写shell脚本时,经常会遇到自己的一个脚本需要调用到自己同目录下另一个脚本的情况,那么如何能在脚本中拿到我们脚本所在的路径呢?
update:20160127
BASH_SOURCE[0]
这个变量只在bash下有效,所以从脚本可移植性的角度来看,
- 对于可以调用子shell执行的脚本
在脚本开头加入#!/bin/bash
来指定解释器,然后使用${BASH_SOURCE[0]}
方式获取脚本所在路径 - 对于需要加载到当前shell环境执行的脚本
使用$0
这种方式获取脚本所在路径,这种方式不能适用于source执行的脚本是link文件
update: 20160126
发现readlink会将指定的文件以绝对路径格式输出,那么就不需要cd到当前目录,然后pwd输出绝对路径了,因此精简如下:
最终方案
SCRIPT=$(readlink -f "${BASH_SOURCE[0]}")
SCRIPTPATH=$(dirname $(SCRIPT))
当然,前提是readlink
这个软件的存在,目前在各个主流linux发行版中都有这个软件,其他BSD发行版未测试
如果没有这个软件,我们也可以用如下方式来获取,当然这种方式无法识别link文件的真实路径
SCRIPTPATH=$(cd $(dirname "${BASH_SOURCE[0]") && pwd )
原文:
具体的情况我们可以按照以下来细分:
注:假设/home/$USER/bin/test.sh为我们自己的脚本
根据当前脚本的文件性质,可分为以下两种情况:
-
link文件:
/usr/local/bin/test.sh -> /home/$USER/bin/test.sh
-
普通可执行文件:
/home/$USER/bin/test.sh
根据脚本执行的方式,又可分为以下两种情况:
-
子shell执行
$ ./test.sh
这种情况又可以根据调用的路径分为以下两种情况:
- 绝对路径调用:
$ /home/$USER/bin/test.sh
- 相对路径调用:
$ bin/test.sh
- 绝对路径调用:
-
父shell执行
$ source test.sh
我们先来看第一种link文件的情况:
linux有两个软件包realpath
与readlink
可以获取到一个link文件的真实路径
使用方法:realpath $filepath
与readlink -f $filepath
其中$filepath是指定的link文件路径,输出为真实路径
还有一种更直接的方式ls -ld $filepath
,然后通过解析输出的内容来获取真实路径
OK,处理完可能是link文件这种情况之后,我们来看看对于普通文件的那些情况:
我们在学习bash的过程中一定遇到过./test.sh
与source test.sh
两种执行脚本的方式,这里简要说明一下:第一种方式bash将会开启一个shell子进程来执行脚本,第二种方式,会在当前shell进程内执行脚本,两种方式的区别之处在于shell子进程所做的操作包括变量的赋值不会影响到父进程(也就是当前进程),举一个很简单的例子:
### test.sh
cd /
如果你用./test.sh
执行时,并不会跳转到/
目录,但是如果你用source test.sh
方式,则会执行cd /
并跳转
区分以上两种情况是因为对于对bash比较熟悉的人来说,获取当前脚本所在的路径的方式第一想到的就是使用参数$0
这种方式,但是这种方式只适用于调用子shell执行脚本的方式,如果你用source
或者.
执行,你就会得到$0
的返回值为bash
这个值,因此使用$0
这种方式就不可取
注:如果你用zsh,你就会发现没有这个问题,
$0
在任何情况下都可以得到当前脚本的相对路径
对于bash而言,还有一个内置的常量可以取得和子shell执行脚本情况下与$0
值一样的效果,这个常量就是BASH_SOURCE
,这是一个数组,用来存储bash执行过程中的一些参数,其中BASH_SOURCE[0]
所代表的就是当前脚本以及它所在的相对路径
注:这种方式有一个缺陷,就是只是bash支持,对于脚本的扩展性不是很好
OK,现在我们可以有效的拿到当前执行脚本的相对路径了,但是这个相对路径还有一点需要说明,就是我在前边区分的绝对路径
与相对路径
的这两种情况:
也就是调用脚本方式的不同,绝对路径与相对路径大家都很应该了解,对于脚本执行效果没有什么区别,但是对于获取脚本路径来说,区别就不一样了,可以使用以下脚本验证
### test.sh
echo "\${BASH_SOURCE[0] = ${BASH_SOURCE[0]}"
我们在bin目录下直接执行./test.sh
,我们可以得到以下输出:
${BASH_SOURCE[0] = ./test.sh
如果在bin目录下以绝度路径执行/home/$USER/bin/test.sh
,我们会获得以下输出:
${BASH_SOURCE[0] = /home/$USER/bin/test.sh
从以上实验结果我们可以看出,如果我们在不同目录下以相对路径调用脚本,那么得出的${BASH_SOURCE[0]}
就会不一样,聪明的你或许会想到我们可以使用pwd
或者$PWD
来获取当前的路径,然后与${BASH_SOURCE[0]}
获取的路径拼凑一下就可以得到,就像这样$PWD/${BASH_SOURCE[0]}
,没错,这样的方式确实能在这种情况下获取,但如果我们用绝对路径调用脚本,你就会发现完全当前的路径出现了两遍
比如说,我们在bin目录下使用绝对路径调用test.sh,~/bin $ /home/$USER/bin/test.sh
那么$PWD/${BASH_SOURCE[0]}
的输出就是这样的/home/$USER/bin/home/$USER/bin/test.sh
,当然,如果你能保证你每次调用这个脚本的时候都是使用相对路径,那么完全可以这样,但是10天之后呢,20天之后甚至一个月之后呢,所以这种方式也不可取
综合以上各种情况,我们
- 使用
${BASH_SOURCE[0]}
来避免source
或.
执行方式的干扰 - 使用
readlink -f $file
来避免file是link文件的干扰 - 对于绝对路径与相对路径的干扰,我们可以通过cd与pwd的方式巧妙获取
$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)
最后我们得到了一个几乎完美的获取方式:
SCRIPTPATH=$(cd $(dirname $(readlink -f "${BASH_SOURCE[0]}")) && pwd )
进一步的思考:
对于脚本中我们要引用到一些其他脚本,
是用绝对路径好呢?还是cd进去执行合适?
那么我们是在当前路径下执行好呢,还是cd到一个临时目录执行好呢?