关于bash:获取脚本自身路径

9次阅读

共计 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_SOURCEFUNCNAME 都是数组。它们跟我本人轻易定义的数组

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 命令或函数的参数值里有可能蕴含空格字符时,就要认真的给它们加上双引号,即便双引号有冗余也没问题。

正文完
 0