共计 3860 个字符,预计需要花费 10 分钟才能阅读完成。
在写 gar 脚本的时候,我须要在 gar 脚本在运行时确定它本身在文件系统中所处目录的门路。基于该门路,可将 gar.css 文件部署到文档我的项目的根目录下,因为 gar.css 与 gar 脚本在同一目录下,后者须要依据本身的地位方能找到它,否则就只能由 gar 脚本的用户提供 gar.css 的门路,有所不便。
BASH_SOURCE
有人说,用以下语句可取得脚本本身门路
my_path=$(cd $(dirname "BASH_SOURCE[0]") && pwd)
我思考了一会。真的思考了……甚至还看了看 Bash reference manual 里对 BASH_SOURCE
的形容:
这个形容里有 FUNCNAME
,我又在 manual 里找到了以下形容:
谁晓得它们在说什么呢?目前,我只晓得 BASH_SOURCE
是个数组变量,存储了一组源文件名(Source filename)。百闻不如一见,我须要 echo
一下 ${BASH_SOURCE[0]}
:
#!/bin/bash
echo ${BASH_SOURCE[0]}
我将上述脚本取名为 foo,将其置于我定义的一个专用于寄存脚本的目录内,并赋其可执行权限,而后执行 foo,
$ foo
/home/garfileo/.my-scripts/foo
换一种形式执行 foo,
$ bash /home/garfileo/.my-scripts/foo
/home/garfileo/.my-scripts/foo
再换一种形式,
$ bash $(foo)
/home/garfileo/.my-scripts/foo
再换一种形式,
$ source $(foo)
/home/garfileo/.my-scripts/foo
在 Bash 的语法里,$(...)
称为命令替换,即开拓一个子 Shell,在其中执行括号内的命令,并以文本的模式返回后果。
上述试验揭示的是,foo 脚本在运行时,能够确定本人身处何处。foo 能够,gar 也能够。
BASH_SOURCE[0]
与 $0
有什么区别?
仿佛通过命令行的第一个参数 $0
也能令一个脚本获知本身所处地位。将 foo 改为
#!/bin/bash
echo ${BASH_SOURCE[0]}
从新做一遍试验:
$ foo
/home/garfileo/.my-scripts/foo
$ bash $(foo)
/home/garfileo/.my-scripts/foo
$ source $(foo)
bash
上述试验中,仅仅是在应用 source
执行 foo
时,失去的后果不是 foo 脚本的地位,而是 bash
。
source
命令有何特别之处?
source
命令
source
命令可载入指定的 Shell 脚本(能够是 Bash,也能够其余 Shell),并将其作为以后正在运行的 Shell 的一部分。因而,上一节的试验
$ source $(foo)
输入的是 bash
,这是因为 foo 脚本中的内容被 source
载入到了以后的 bash 里,成为了后者的一部分,因此 $0
便不是 foo,而是 bash。
source
命令就是 Bash 世界里的吸星大法或北冥神功。
因为存在 source
这样的命令,因而用 ${BASH_SOURCE[0]}
取得脚本的本身门路更为持重。
函数栈
大抵了解了 ${BASH_SOURCE[0]}
的奥义,我仍然有些纠结 Bash reference manual 给出的解释:
函数
${FUNCNAME[$i]}
在文件${BASH_SOURCE[$i]}
里定义,在${BASH_SOURCE[$i+1]}
中被调用。
是什么意思呢?
具体一下,即:函数 ${FUNCNAME[0]}
在文件 ${BASH_SOURCE[0]}
里定义,在 ${BASH_SOURCE[1]}
中被调用。是什么意思呢?
我须要批改 foo,在其中定义一个简略的函数:
#!/bin/bash
function test {echo ${BASH_SOURCE[*]}
echo ${FUNCNAME[*]}
}
其中,${X[*]}
的意思是,将数组 X
里的所有的货色合并为一段文本(或一段字串)。
而后,创立脚本 bar,用它 source foo
,并调用函数 text
:
#!/bin/bash
source foo
test
而后执行
$ bash ./bar
/home/garfileo/.my-scripts/foo ./bar
test main
依据这个输入后果,很容易推断出
${BASH_SOURCE[0]
是/home/garfileo/.my-scripts/foo
;${BASH_SOURCE[1]
是bar
;${FUNCNAME[0]}
是test
;${FUNCNAME[1]}
是main
。
于是,可断言:函数 test
在文件 foo
里被定义,在文件 bar
里被调用。
为了更明确一些,批改 bar 脚本:
#!/bin/bash
source foo
function bar {test}
bar
再次执行 bar:
$ bash ./bar
/home/garfileo/.my-scripts/foo ./bar ./bar
test bar main
能够据此推断出,main
函数是函数栈最底部的函数,它是 Bash 所有函数的调用者。
数组
BASH_SOURCE
和 FUNCNAME
都是数组。它们跟我本人轻易定义的数组
blab=(a b c d e f)
在实质上并无区别。${blab[0]}
,${blab[1]}
,${blab[*]}
……尽管看上去奇怪,但皆为从数组里获取元素的语法。
${blab[0]}
从 blab
里获取第 1 个元素。${blab[1]}
从 blab
里获取第 2 个元素。顺次类推。尽管 Bash 数学不是很好,更像个文科生,然而在数组的下标方面,反对算术,例如 ${blab[1+2]}
从 blab
里获取第 4 个元素。
上文已有讲述,${blab[*]}
从 blab
里获取所有元素,并组织成一段文本。可能获取数组所有元素的语法,还有 ${blab[@]}
,然而它失去的是多段的文本,相邻的文本段以 IFS
定义的符号隔开。IFS
是 Bash 的一个环境变量,它的值默认是空格。因而,多段文本即空格作为距离符号的文本。
多段文本,在 Bash 的循环语句里很是常见。例如
for i in a b c d e
do
echo $i
done
这段代码可在终端窗口(命令行窗口)里输入
a
b
c
d
e
因为 ${blab[@]}
能获取 blab
中的所有元素并以分段文本的模式将其给出,因而上述循环语句等价于以下代码
for i in ${blab[@]}
do
echo $i
done
然而,如果数组是上面这样
blab=(a b c d "e f g" h)
${blab[@]}
并不认为 "e f g"
是数组里的一个元素,而是三个。要束缚一下它的放荡,就须要用双引号。例如
for i in "${blab[@]}"
do
echo $i
done
这段代码会产生输入:
a
b
c
d
e f g
h
在数组元素的拜访语句外围加双引号的行为,仿佛没有什么法则可言。这样的模式,就相似于英文里某些单词的过来时态那样非凡。
命令或函数的参数
命令或函数的参数,也是数组,只是在拜访其中元素的语法更简化了。例如,$0, $1, $2, ...
,能够拜访第 1 个,第 2 个,第 3 个,第……个参数。应用 $@
可拜访全副的参数,然而失去的是分段文本。应用 $*
也能够拜访全副的参数,然而失去的是一段文本。
我感觉挺实用的是数组的切片语法。例如
${blab[@]:2}
示意获取 blab
里第 3 个元素及其之后的所有元素,后果以分段文本的模式给出。
${blab[@]:2:4}
示意获取 blab
里第 3 个元素及其之后的 3 个元素,一共是 4 个元素。
同理,对于命令或函数的参数,也有相似的语法:
${@:2}
${@:3:4}
身在何处
如同曾经有些偏离了问题的出发点太远。当初,回到终点:
my_path=$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)
我曾一度狐疑这行代码有些罗嗦,写成
my_path=$(dirname ${BASH_SOURCE[0]})
不行吗?
例如,依据后面的试验,${BASH_SOURCE[0]}
能够给出 foo 脚本所在位置的绝对路径:
/home/garfileo/.my-scripts/foo
假使对这个门路执行 dirname
,变可失去 foo 脚本的地位,即
$ dirname /home/garfileo/.my-scripts/foo
/home/garfileo/.my-scripts
用同样的方法,便可在 gar 脚本里齐全能够确定它本身在何处了,为何还要 cd
到 $(dirname ${BASH_SOURCE[0]}
,而后再 pwd
呢?
上述想法之所以能得手,是一种偶合。因为我将 foo 脚本放到了零碎 PATH
设定的门路里。Bash 在执行 foo 的时候,可能失去它的绝对路径,并保留至 ${BASH_SOURCE[0]}
。假使执行 foo 的时候,应用的是相对路径,上述想法便会失灵。所以,不应该投机取巧,老老实实先跳转到 $(dirname ${BASH_SOURCE[0]}
,胜利后,再用 pwd
输入以后的工作目录。因为 Bash 的命令替换开启的是以后 Shell 的子 Shell,而在子 Shell 里切换工作目录,并不会影响以后 Shell。
事实上,思考到有时会遇到目录或文件的名字里蕴含空格的状况,获取脚本本身门路更持重的写法应该是:
my_path="$(cd"$(dirname "${BASH_SOURCE[0]"})"&& pwd)"
记住,空格是 Bash 命令和函数的天敌。但凡狐疑 Bash 命令或函数的参数值里有可能蕴含空格字符时,就要认真的给它们加上双引号,即便双引号有冗余也没问题。