写在前面
如果你问我bash的这么多扩展哪个功能最强大,那我会毫不犹豫地告诉你,当然是参数扩展啦~
为什么说参数扩展功能强大呢?那是因为通过参数扩展功能,我们可以完成很多意想不到的功能,例如可以完成参数值的删除、截取以及替换等功能~
SHELL参数以及参数的分类
开始讲述参数扩展之前,我们先要了解什么是shell的参数,以及引用参数的不同方法。
其实在shell编程中,参数(parameter)是个大概念,也是个笼统的概念,在bash手册中对参数的定义只是一句话:
A parameter is an entity that stores values.
意思是说在shell中参数是个实体(entity),这个实体中存储着各式各样的值(values)。
紧接着提到:
It can be a name, a number, or one of the special characters listed below under Special Parameters.
这句话其实是告诉我们,可以通过三类方式来引用参数,从而得到参数中存储的值。根据引用方式的不同,可以将参数分为三类,归纳如下:
(1)通过名称(name)来引用参数,这样的参数我们称之为变量(variables )。一个变量拥有自己的值和诸多属性,属性可以通过declare来设定,可以通过unset来取消一个变量。
(2)通过数字(number)来引用参数,这样的参数我们称之为位置参数(Positional Parameters)。位置参数在脚本被调用时自动初始化为传递给脚本的参数。脚本中调用函数时,位置参数会暂时替换成传递给函数的参数。我们可以使用set命令来改变位置参数的值,但是不能试图通过赋值语句来改变位置参数的值。(参考《Handling positional parameters》)
(3)最后还有一类参数,被称之为特殊参数(Special Parameters)。特殊在哪里? 特殊在我们只能通过shell内部预定义的特殊符号来引用它们,并且我们只能引用,不能试图通过赋值语句来重新赋值。预定义的特殊符号包括:* @ $ ? ! - $ 0 (参考《Special parameters and shell variables》)
(参考链接:《Parameter》《Parameters》)
什么是参数扩展呢?
讲白了,所谓参数扩展就是通过符号$获得参数中存储的值。只不过呢,在获得最终的结果之前,允许我们对参数以及参数值做很多操作,例如本文一开始就提到的对参数值进行删除、截取以及替换等操作~
本篇博文就是详细讨论参数扩展过程中我们可以进行的诸多操作。
最简单的形式
参数扩展最简单直接的形式如下:
$parameter
或者
${parameter}
个人倾向于后者,第一有花括号一看就知道是参数扩展,其次可以根据需要在右花括号后头追加字符(串),否则shell会认为是参数的一部分。举个小例子就知道了,如下:
[09:49:23@astrol:~]$ WORD=car
[09:49:25@astrol:~]$ echo "The plural of $WORD is most likely $WORDs"
The plural of car is most likely
[09:49:27@astrol:~]$ echo "The plural of $WORD is most likely ${WORD}s"
The plural of car is most likely cars
可以看到不加花括号的话,shell认为WORDs是参数,然而我们并没有设置过这个参数,因此扩展结果为空。
需要注意的是,在bash中引用位置参数时,大于第9个参数时,两位的数字要求必须要在花括号内。例如:${10}
另外,后文介绍的各种操作都是需要在花括号内进行的。
使用默认值(Use Default Values)
test
赋值默认值(Assign Default Values)
test
间接扩展(indirect expansion)
间接扩展也被很多人称为间接引用。如果熟悉C/C++的话,可以简单的把间接扩展理解成指针变量。
子串扩展(Substring Expansion)
${parameter:offset}
${parameter:offset:length}
子串扩展的意思是从offset位置开始截取长度为length的子串,如果没有提供length,则是从offset开始到结尾。需要注意的几点是:
(1)如果offset是个负值,开始位置是从字符串末尾开始算起,然后取长度为length的子串。例如,-1代表是从最后一个字符开始。
(2)如果length是个负值,那么length的含义不再代表字符串长度,而是代表另一个offset,位置从字符串末尾开始,扩展的结果是offset ~ length之间的子串。
(3)如果parameter是@,也就是所有的位置参数时,offset必须从1开始。
来看几个例子:
[17:58:36@astrol:~]$ MYSTRING="Be liberal in what you accept, and conservative in what you send"
[17:59:19@astrol:~]$ echo ${MYSTRING}
Be liberal in what you accept, and conservative in what you send
[17:59:30@astrol:~]$ echo ${MYSTRING:34}
conservative in what you send
[17:59:42@astrol:~]$ echo ${MYSTRING:34:13}
conservative
[17:59:56@astrol:~]$ echo ${MYSTRING: -10:5}
t you
[18:00:18@astrol:~]$ echo ${MYSTRING:(-10):5}
t you
[18:00:23@astrol:~]$ echo ${MYSTRING:11:-17}
in what you accept, and conservative
可以看到当offset是负值时,负号(-)必须与冒号(:)有间隔,这是为了避免与上文提到的${parameter:-word} 混淆。
查找和替换(Pattern substitution)
${parameter/pattern/string}
${parameter//pattern/string}
${parameter/pattern}
${parameter//pattern}
pattern和路径扩展(pathname expansion)中的模式匹配(pattern matching)一样(详情可参考文章《bash之通配符》),匹配后的子串会用string替换掉。需要注意的有以下几点:
(1)parameter之后如果是/,则只替换匹配到的第一个子串;parameter之后如果是//,则替换所有匹配到的子串。
(2)当string为空时,则相当于将匹配的子串删除。
(3)特殊符号#和%在这种情况下分别锚定(Anchoring )字符串的开始和结尾。
(4)如果bash的nocasematch选项参数是打开的(shopt -s nocasematch),则匹配的过程大小写是不敏感的。
例子如下:
[19:26:39@astrol:~]$ MYSTRING="Be liberal in what you accept, and conservative in what you send"
[19:26:40@astrol:~]$ echo ${MYSTRING}
Be liberal in what you accept, and conservative in what you send
[19:26:46@astrol:~]$ echo ${MYSTRING//conservative/happy}
Be liberal in what you accept, and happy in what you send
[19:27:04@astrol:~]$ echo ${MYSTRING/in/by}
Be liberal by what you accept, and conservative in what you send
[19:27:21@astrol:~]$ echo ${MYSTRING//in/by}
Be liberal by what you accept, and conservative by what you send
[19:27:24@astrol:~]$ echo ${MYSTRING/conservative/}
Be liberal in what you accept, and in what you send
[19:27:50@astrol:~]$ MYSTRING=xxxxxxxxxxxxxxx
[19:28:09@astrol:~]$ echo ${MYSTRING}
xxxxxxxxxxxxxxx
[19:28:17@astrol:~]$ echo ${MYSTRING/#x/y}
yxxxxxxxxxxxxxx
[19:28:26@astrol:~]$ echo ${MYSTRING/%x/y}
xxxxxxxxxxxxxxy
bash的这个查找替换功能跟sed的很像,不同的是这里的pattern不是正则表达式。
查找并删除(Remove matching prefix/suffix pattern)
${parameter#pattern}
${parameter##pattern}
${parameter%pattern}
${parameter%%pattern}
删除匹配到的子串。直接来看例子吧,假设我们定义了一个变量为:
file=/dir1/dir2/dir3/my.file.txt
那么:
${file#*/}:删除第一个 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt
${file##*/}:删除最后一个 / 及其左边的字符串:my.file.txt , 相当于basename ${file}
${file#*.}:删除第一个 . 及其左边的字符串:file.txt
${file##*.}:删除最后一个 . 及其左边的字符串:txt
${file%/*}:删除最后一个 / 及其右边的字符串:/dir1/dir2/dir3,相当于dirname ${file}
${file%%/*}:删除第一个 / 及其右边的字符串:(空值)
${file%.*}:删除最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file
${file%%.*}:删除第一个 . 及其右边的字符串:/dir1/dir2/dir3/my
记忆的方法为:
# 是去掉左边(在键盘上 # 在 $ 之左边)
% 是去掉右边(在键盘上 % 在 $ 之右边)
单一符号是最小匹配﹔两个符号是最大匹配。
获取参数值长度(Parameter length)
${#parameter}
这个扩展很简单,就是返回parameter值的长度值。
[20:49:27@astrol:~]$ MYSTRING="Be liberal in what you accept, and conservative in what you send"
[20:49:27@astrol:~]$ echo ${MYSTRING}
Be liberal in what you accept, and conservative in what you send
[20:49:34@astrol:~]$ echo ${#MYSTRING}
64
大小写转换(Case modification)
${parameter^}
${parameter^^}
${parameter,}
${parameter,,}
字符^意思是将第一个字符转换成大写字母,^^的意思是将所有的字符转换成大写字母。
字符,意思是将第一个字符转换成小写字母,,,的意思是将所有的字符转换成小写字母。
来看如下例子:
[20:00:49@astrol:~]$ lower="lowercase letters"
[20:00:55@astrol:~]$ echo ${lower}
lowercase letters
[20:01:08@astrol:~]$ echo ${lower^}
Lowercase letters
[20:01:12@astrol:~]$ echo ${lower^^}
LOWERCASE LETTERS
[20:01:15@astrol:~]$ echo ${lower} | tr '[:lower:]' '[:upper:]'
LOWERCASE LETTERS
[20:01:43@astrol:~]$
[20:01:49@astrol:~]$ UPPER="UPPER LETTERS"
[20:02:01@astrol:~]$ echo ${UPPER}
UPPER LETTERS
[20:02:07@astrol:~]$ echo ${UPPER,}
uPPER LETTERS
[20:02:09@astrol:~]$ echo ${UPPER,,}
upper letters
[20:02:11@astrol:~]$ echo ${UPPER} | tr '[:upper:]' '[:lower:]'
upper letters
关于大小写转换的更多例子可以参考《Shell Script: Convert Lowercase to Uppercase》
参考链接:
《BASH: Parameter expansion》(需梯子)
《Linux 技巧: Bash 参数和参数扩展》《Bash parameters and parameter expansions》
《Linux Shell参数扩展(Parameter Expansion)》
《Bash parameters and parameter expansions》