之前讨论Bash时,其实刻意地略过了两个“不太起眼”的细节,一个是引号的使用,尤其是双引号,另一个是命令与参数以及参数与参数之间的分隔方式(术语叫做分词)。这一节我们就针对性地探讨下这两部分内容。
引号的作用
Bash中有三种引号,单引号、双引号和反引号(`)。反引号不必多说,它只用于命令替换语法;单引号也比较简单,就是表示纯粹的字符串;只有双引号比较特别,有一些特定的用途,它的特殊之处可以归纳为如下几点:
-
在双引号中,
$和`保留它们的特殊语义,因此Bash扩展(参数扩展${},算术扩展$(()))和命令替换($()和`command`)仍然有效,比如[ken ~]$echo "$var" 123 [ken bash]$echo "`date`" 2023年 05月 08日 星期一 21:15:52 CST [ken ~]$echo '$var' $var -
条件表达式的字符串比较,如果
[[ $var == str ]]中的str没有双引号包裹,Bash会按照模式匹配的方式去解释str(?,*和[]等字符具有模式匹配语义)。这一点我们在前面Bash基本表达式(二)简单命令、列表和条件表达式(模式匹配)一节介绍过,这里不再举例。 -
位置参数
$@和$*的展开
我们在前面函数一节里曾提到过这个,现在探究下其中的细节。我们知道,$@和$*代表所有的位置参数,所以我们可以直接用for语句对位置参数进行遍历并打印for para in $@ do echo $para done可当我们用双引号把他们包起来时,
"$@"和"$*"应该返回什么呢?一般而言,双引号包裹起来的内容会返回一个字符串,那么一个绕不开的问题是,这个字符串是什么样子的,是直接把所有的位置参数前后收尾相连呢,还是说在中间放一个空格或者Tab互相隔开?这个问题的答案,涉及到分词,下面来看看
分词
分词(Bash手册关键字Word Splitting),用于把命令行上的输入,切分为一个个的词(word)。当我们输入下面这个命令时
echo Hello world
Hello world
Bash会把它分为三个词,echo,Hello和world,第一个词会被解释为命令(command),第二第三个解释为参数。Bash如何知道怎么去切分词的呢?“当然是用空格啊,傻瓜”,你可能心里嘀咕着。这话没错儿,但是不全对,因为我们偶尔用Tab键来分隔,比如在脚本hello.sh里写上
echo Hello world
执行脚本
[ken bash]$bash hello.sh
Hello world
除了Tab键,我们还有换行符,上面的hello.sh还可以改写为
echo \
Hello \
world
其中的\+回车表示换行符的意思(\起到了转义的效果,跟C语言一样)。所以,分词时究竟遵循什么规则呢?答案是IFS。IFS(Internal Field Separator,内部域分隔符)是Bash的内置变量,这个变量的值是切分词的依据。IFS默认值包括三个空白字符,
[ken@Ken-Laptop ~]$ echo $IFS
[ken@Ken-Laptop ~]$ echo ${#IFS}
3
因为IFS的值都是空白符,所以不好显示,我们可以用printf命令来写
[ken@Ken-Laptop ~]$ printf "%q\n" "$IFS"
$' \t\n'
这里有两个地方值得说明下,一个是printf的格式化字符串%q,它的作用是把一些不可打印字符(比如空白符、ASCII控制字符)按照POSIX $''的转义语法进行打印,另一个自然就是POSIX $''语法的使用,它用于将不可打印字符通过\转义序列的方式来表示(详细的转义序列可查询Bash手册关键字QUOTING),比如说
[ken bash]$a="\n"
[ken bash]$echo "$a"
\n
[ken bash]$b=$'\n'
[ken bash]$echo "$b"
[ken bash]$echo ${#a}
2
[ken bash]$echo ${#b}
1
可见a的值由两个字符\和n组成,没有\转义;而b的值为一个字符,也就是换行符,发生了\转义。
接着回到分词的正题,如果说IFS的值被修改了,分词的效果会有什么变化呢?我们举个例子来说明下
function printParams()
{
echo There are $# parameters
echo Positional parameters using '$*' are: "$*"
echo Positional parameters using '$@' are: "$@"
}
IFS="-$IFS"
echo IFS的长度为${#IFS}, 值为"$IFS"
echo "Round 1 start"
printParams a b c d-e-f-g
echo
echo "Round 2 start"
params="a b c d-e-f-g"
printParams $params
输出结果为
IFS的长度为4, 值为-
Round 1 start
There are 4 parameters
Positional parameters using $* are: a-b-c-d-e-f-g
Positional parameters using $@ are: a b c d-e-f-g
Round 2 start
There are 7 parameters
Positional parameters using $* are: a-b-c-d-e-f-g
Positional parameters using $@ are: a b c d e f g
OK,这个输出结果可能确实有点让人摸不着头脑,让我们来分析下。首先我们修改Bash内置变量IFS的值,给它在前面新增了个-,所以这时后IFS字符串的长度从3变为4。按照IFS的定义,此时-跟空格一样,也应该可以用于分词才对。所以我们接下来用函数printParams试验了两轮(Round),第一轮直接把参数字符串a b c d-e-f-g传给函数,第二轮我们设置了一个临时变量params并把参数字符串a b c d-e-f-g赋值给它,之后再通过变量值(参数展开)传递给函数printParams。打印结果表明,第一轮里面Bash认为有4个参数,分词符-并没有发生作用,第二轮里Bash认为有7个参数,分词符-发生作用。为什么会发生这种区别呢?答案是,分词只有在Bash扩展、命令替换等语法里才会生效。哇,真让人有点头大~
参数的个数讨论完,还有参数值"$*"和"$@"的打印结果没探讨,这里把答案告诉大家:
"$*"返回的字符串为,先把所有的位置参数展开,再将各参数用IFS值的第一个字符连接后返回,最终结果为$1c$2c$3...,其中c为IFS变量值的第一个字符
"$@"实际上返回了多个字符串,其结果为"$1" "$2" "$3"...,每个字符串之间用空格隔开
也即是说,在"$*"中IFS的值扮演了拼接的角色,最后只返回一个字符串,而在"$@"中IFS不参与,并且最后返回多个以空格分隔的字符串。现在你能解释上面Round 1和Round 2的参数值打印结果了吗?^_^

浙公网安备 33010602011771号