-
Notifications
You must be signed in to change notification settings - Fork 1
Bash 脚本编程的一些高级用法
管道#
有一点shell
编程基础的应该都知道管道。这是一个或多个命令的序列,用字符|
分隔。实际上,一个完整的管道格式是这样的:
Copy[time [-p]] [ ! ] command [ | command2 ... ]
time
单独执行某一条命令非常容易理解,统计这个命令运行的时间,但管道这种多个命令的组合,他统计的是某一个命令的时间还是管道所有命令的时间呢?如果保留字 time 作为管道前缀,管道中止后将给出执行管道耗费的用户和系统时间。
如果保留字 !
作为管道前缀,管道的退出状态将是最后一个命令的退出状态的逻辑非值。 否则,管道的退出状态就是最后一个命令的。 shell 在返回退出状态值之前,等待管道中的所有命令返回。
复合命令#
我们常见的case ... in ... esac
语句,if ... elif ... else
语句,while .... do ... done
语句,for ... in ...; do ... done
,甚至函数function name() {....}
都属于复合命令。
for
循环常见的完整格式是:
Copyfor name [ in word ] ; do list ; done
除此之外,其实还支持类似与C语言的for循环,
Copyfor (( expr1 ; expr2 ; expr3 )) ; do list ; done
返回值是序列 list 中被执行的最后一个命令的返回值;或者是 false,如果任何表达式非法的话。
man bash
上显示,case
语句的完整格式是case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac
。
展开后应该是这样的:
Copycase word in [(] pattern [ | pattern ]) list ;; ... esac
每一个case
的分支,都是pattern
,使用与路径扩展相同的匹配规则来匹配,见下面的 路径扩展 章节,且通过|
支持多种匹配走同一分支。例如:
Copycase ${val} in *linux* | *uboot* ) ... ;; ... esac
如果找到一个匹配,相应的序列将被执行。找到一个匹配之后,不会再尝试其后的匹配。
如果没有模式可以匹配,返回值是 0。否则,返回序列中最后执行的命令的返回值。
select
语句可以说用得很少,但其实在需要交互选择的场景下非常实用。它的完整格式是:
Copyselect name [ in word ] do list done
它可以显示出带编号的菜单,用户输入不同的编号就可以选择不同的菜单,并执行不同的功能。我们看一个例子:
Copy#!/bin/bash echo "What is your favourite OS?" select name in "Linux" "Windows" "Mac OS" "UNIX" "Android" do echo "You have selected $name" done
运行结果是这样的:
CopyWhat is your favourite OS? 1) Linux 2) Windows 3) Mac OS 4) UNIX 5) Android #? 4↙ You have selected UNIX #? 1↙ You have selected Linux #? 9↙ You have selected #? 2↙ You have selected Windows #?^D
#?
用来提示用户输入菜单编号,这实际是环境变量PS3
的值,可以通过改这变量来改用户提示信息。^D
表示按下 Ctrl+D
组合键,它的作用是结束 select
循环。
如果用户输入的菜单编号不在范围之内,例如上面我们输入的 9,那么就会给 name 赋一个空值;如果用户输入一个空值(什么也不输入,直接回车),会重新显示一遍菜单。
注意,select 是无限循环(死循环),输入空值,或者输入的值无效,都不会结束循环,只有遇到 break 语句,或者按下 Ctrl+D 组合键才能结束循环。通常和 case in 一起使用,在用户输入不同的编号时可以做出不同的反应。例如
Copyecho "What is your favourite OS?" select name in "Linux" "Windows" "Mac OS" "UNIX" "Android" do case $name in "Linux") echo "Linux是一个类UNIX操作系统,它开源免费,运行在各种服务器设备和嵌入式设备。" break ;; "Windows") echo "Windows是微软开发的个人电脑操作系统,它是闭源收费的。" break ;; ...... *) echo "输入错误,请重新输入" esac done
( list )
会让 list
序列将在一个子 shell 中执行。变量赋值和影响 shell 环境变量的内建命令在命令结束后不会再起作用。返回值是序列的返回值。
这个在需要临时切换目录或者改变环境变量的情况下非常使用。例如封装编译内核的命令,实现任何目录下都可以直接编译,我们总需要先cd
到内核根目录,再make
编译,最后再cd
回原目录。例如:
Copyalias mkernel='cd ~/linux ; make -j4 ; cd -'
这样会导致,在编译过程如果Ctrl + C
取消返回时,你所处在的目录就变成了~/linux
。这种情况下,使用( list )
就能解决这问题,甚至都不需要cd -
返回原目录,直接退出即可。
Copyalias mkernel='(cd ~/linux ; make -j4)'
也例如,有某个程序比较挫,只能在程序目录执行,在其他目录,甚至上一级目录执行,都会找不到资源文件导致退出,我们可以这样解决:
Copyalias xmind='(cd ~/软件/xmind/XMind_amd64 &>/dev/null && nohup ./XMind &>/dev/null) &'
表达式 expression
将被求值。如果表达式的值非零,返回值就是 0;否则返回值是 1。这种做法和 let "expression" 等价。
expression 语句
在 if
语句中,我们喜欢用 if [ expression ]; then ... fi
的单括号的形式,但看大神们的脚本,他们更常用if [[ expression ]]; then ... fi
的双括号形式。
[ ... ]
等效于test
命令,而[[ ... ]]
是另一种命令语法,相似功能却更高级,它除了传统的条件表达式(Eg. [ ${val} -eq 0 ])外,还支持表达式的转义,就是说可以像在其他语言中一样使用出现的比较符号,例如>
,<=
,&&
,||
等。
举个例子,要判断变量val
有值且大于4,用单括号需要这么写:
Copy[ -n ${val} -a ${val} -gt 4 ]
用双括号可以这么写:
Copy[[ -n ${val} && ${val} > 4 ]]
当使用==
和!=
操作符时,操作符右边的字符串被认为是一个模式,根据下面 模式匹配 章节中的规则进行匹配。如果匹配则返回值是 0,否则返回1。模式的任何部分可以被引用,强制使它作为一个字符串而被匹配。
引用#
这里主要讲的是$'string'
特殊格式,注意的是,必须是单引号。它被扩展为string
,其中的反斜杠转义字符被替换为 ANSI C 标准中规定的字符。反斜杠转义序列,如果存在的话,将做如下转换:
转义 | 含义 |
---|---|
\a | alert (bell) 响铃 |
\b | backspace 回退 |
\e | an escape character 字符 Esc |
\f | form feed 进纸 |
\n | new line 新行符 |
\r | carriage return 回车 |
\t | horizontal tab 水平跳格 |
\v | vertical tab 竖直跳格 |
\ | backslash 反斜杠 |
' | single quote 单引号 |
\nnn | 一个八比特字符,它的值是八进制值 nnn (一到三个数字) |
\xHH | 一个八比特字符,它的值是十六进制值 HH (一到两个十六进制数字) |
\cx | 一个 ctrl-x 字符 |
${!var}
是间接扩展。bash
使用以 var
的其余部分为名的变量的值作为变量的名称; 接下来新的变量被扩展,它的值用在随后的替换当中,而不是使用var
自身的值。
有点拗口,举个例子就懂了
Copy$ var_name=val $ val="Bash expansion" $ echo ${!var_name} Bash expansion
所以,${!var_name}
等效于${val}
,就是取val_name
的值作为变量名,再获取新变量名的值。
!
有一种例外情况,那就是${!prefix*}
,下面再介绍。
${!prefix*}
实现扩展为名称以 prefix 开始的变量名,以特殊变量 IFS 的第一个字符分隔。换句话说,这种用法就是用于获取变量名的。例如:
Copy# 创建3个以VAR开头的变量 $ VAR_A=a $ VAR_B=b $ VAR_C=c # 寻找以VAR开头的变量名 $ echo ${!VAR*} VAR_A VAR_B VAR_C
${#parameter}
用于获取变量的长度。如果 parameter
是*
或者是 @
, 替换的值是位置参数的个数。如果 parameter
是一个数组名,下标是 *
或者是 @
, 替换的值是数组中元素的个数。
${parameter:-word}
表示使用默认值。如果 parameter
未定义或值为空,将替换为 word
的扩展。否则,将替换为 parameter 的值。
${parameter:=word}
赋默认值。如果 parameter
未定义或值为空, word
的扩展将赋予 parameter
。parameter
的值将被替换。位置参数和特殊参数不能用这种方式赋值。
${parameter:=word}
和${parameter:-word}
有什么差别?还是举个例子:
Copy# 删除var变量 $ unset var # 确认var变量为空 $ echo ${var} # 当var为空时,把test赋值给var,同时返回test $ echo ${var:=test} test # 可以看到,此时var已经被赋值 $ echo ${var} test # 再次删除var变量,继续实验 $ unset var # 当var为空时,返回test $ echo ${var:-test} test # 对比验证,此时var并没有赋值 $ echo ${var}
所以,差别在于,当parameter
为空时,${parameter:=word}
会比${parameter:-word}
多做一步,就是把word
的值赋给parameter
。
${parameter:?word}
主要用于当parameter
为空时,显示错误信息word
。shell
如果不是交互的,则将退出。
如果 parameter 未定义或非空,不会进行替换;否则将替换为 word 扩展后的值。这与${parameter:-word}
完全相反。简单来说,就是当parameter
非空时,才使用word
。
同 ${parameter:offset:length}
${parameter:offset:length}
可以实现字符串的截取,从offset
开始,截取length
个字符。如果 offset 求值结果小于 0, 值将当作从 parameter
的值的末尾算起的偏移量。如果parameter
是 @
,结果是 length
个位置参数,从 offset
开始。 如果 parameter
是一个数组名,以 @
或 索引,结果是数组的
length
个成员,从 ${parameter[offset]}
开始。 子字符串的下标是从 0 开始的,除非使用位置参数时,下标从 1 开始。
参考 ${parameter##word}
word
支持模式匹配,从parameter
的开始位置寻找匹配,一个#
的是寻找最短匹配,两个#
的是寻找最长匹配,把匹配的内容删除后,把剩下的返回。例如:
Copy$ str="we are testing, we are testing" $ echo ${str#are} testing, we are testing $ echo ${str##are} testing
这必须是从头开始删的,如果要删除中间的某一些字符串,可以用${parameter/pattern/string}
。
如果 parameter
是一个数组变量,下标是@
或者是,模式删除将依次施用于数组中的每个成员,最后扩展为结果的列表。
参考${parameter%%word}
这也是在parameter
中删除匹配的内容后返回。%
与#
非常类似,前者是从头开始匹配,后者是从尾部开始匹配。同样的,一个%
是寻找最短匹配,两个%%
是寻找最长匹配。例如:
Copy$ str="we are testing, we are testing" $ echo ${str%are} we are testing, we $ echo ${str%%are} we
这必须是从末端开始删的,如果要删除中间的某一些字符串,可以用${parameter/pattern/string}
。
如果 parameter
是一个数组变量,下标是@
或者是,模式删除将依次施用于数组中的每个成员,最后扩展为结果的列表。
参考${parameter//pattern/string}
${parameter//pattern/string}
和${parameter/pattern/string}
,主要实现了字符串替换,当然,如果要替换的结果是空,就等效于删除。一个/
,表示只有第一个匹配的被替换,两个/
表示所有匹配的都替换。例如:
Copy$ str="we are testing, we are testing" # 替换首次匹配 $ echo ${str/we are/I am} I am testing, we are testing # 替换所有匹配 $ echo ${str//we are/I am} I am testing, I am testing # 删除首次匹配 $ echo ${str/are/} we testing, we are testing # 删除所有匹配 $ echo ${str//are/} we testing, we testing
如果patten
以#
开始,例如${str/#we are/}
,则必须从头开始就匹配;以%
表示,例如${str/%are testing/}
,必须从末端就要完全匹配。
如果 parameter
是一个数组变量,下标是@
或者是,模式删除将依次施用于数组中的每个成员,最后扩展为结果的列表。
路径扩展#
我们经常会这样使用路径扩展,ls ~/work*
,这里的就是路径匹配的一种,表示匹配包含空串的任何字符串。除了
之外,还有
?
和[
。路径扩展其实运用了模式匹配,所以匹配规则不妨直接看模式匹配。
任何模式中出现的字符,除了下面描述的特殊模式字符外,都匹配它本身。 模式中不能出现 NUL 字符。如果要匹配字面上的特殊模式字符,它必须被引用。
特殊模式字符有下述意义:
-
: 匹配任何字符串包含空串。
-
?
: 匹配任何单个字符。 -
[...]
: 匹配括号内的任意一个字符,与正则匹配一致。
与正则的[...]
一致,[!...]
或者[^...]
表示不匹配括号内的字符;[a-zA-Z]
表示从a到z以及从A到Z的所有字符;也支持[:alinum:]
这类的特殊字符。
如果使用内建命令 shopt 启用了 shell 选项 extglob, 将识别另外几种模式匹配操作符。
-
?(pattern-list)
:匹配所给模式零次或一次出现 -
(pattern-list)
:匹配所给模式零次或多次出现 -
+(pattern-list)
:匹配所给模式一次或多次出现 -
@(pattern-list)
:准确匹配所给模式之一 -
!(pattern-list)
:任何除了匹配所给模式之一的字串
重定向#
简单的重定向不累述了,讲一些高级用法。
Here Documents#
here-document 的格式是:
Copy<<[-]word here-document delimiter
这种重定向使得 shell 从当前源文件读取输入,直到遇到仅包含 word
的一行 (并且没有尾部空白,trailing blanks) 为止。直到这一点的所有行被用作 命令的标准输入。
还是听拗口,咱们看例子:
Copy$ cat <<EOF > fist line > second line > third line > EOF fist line second line third line
上述的做法,把两个EOF
之间的内容作为一个文件,传递给cat
命令。甚至,我们还有更高级的用法,实现动态创建文件。
Copy$ kernel=linux $ cat > ./readme.txt <<EOF > You are using kernel ${kernel} > EOF $ cat ./readme.txt You are using kernel linux
Here Strings#
here-document 的变种,形式是
Copy<<<word
word 被扩展,提供给命令作为标准输入,例如,我希望检索变量的值,有以下两种做法:
Copy$ echo ${var} | grep "test" $ grep "test" <<< ${var}
Opening File Descriptors for Reading and Writing#
重定向操作符,[n]<>word
,使得以 word
扩展结果为名的文件被打开,通过文件描述符 n
进行读写。如果没有指定 n
那么就使用文件描述符 0
。如果文件不存在,它将被创建。
这操作暂时没用过,待补充示例。
总结#
本文结合man bash
以及自己的一些经验,总结了Shell编程的一些高级用法。还是那句话,建议有一定基础的同学学习,毕竟在跑之前要先学会走路不是?
Copy [time [-p]] [ ! ] command [ | command2 ... ] time单独执行某一条命令非常容易理解,统计这个命令运行的时间,但管道这种多个命令的组合,他统计的是某一个命令的时间还是管道所有命令的时间呢?如果保留字 time 作为管道前缀,管道中止后将给出执行管道耗费的用户和系统时间。
如果保留字 ! 作为管道前缀,管道的退出状态将是最后一个命令的退出状态的逻辑非值。 否则,管道的退出状态就是最后一个命令的。 shell 在返回退出状态值之前,等待管道中的所有命令返回。
复合命令# 我们常见的case ... in ... esac语句,if ... elif ... else语句,while .... do ... done语句,for ... in ...; do ... done,甚至函数function name() {....}都属于复合命令。
for 语句 for循环常见的完整格式是:
Copy for name [ in word ] ; do list ; done 除此之外,其实还支持类似与C语言的for循环,
Copy for (( expr1 ; expr2 ; expr3 )) ; do list ; done 返回值是序列 list 中被执行的最后一个命令的返回值;或者是 false,如果任何表达式非法的话。
case 语句 man bash上显示,case语句的完整格式是case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac。
展开后应该是这样的:
Copy case word in [(] pattern [ | pattern ]) list ;; ... esac 每一个case的分支,都是pattern,使用与路径扩展相同的匹配规则来匹配,见下面的 路径扩展 章节,且通过|支持多种匹配走同一分支。例如:
Copy case ${val} in linux | uboot ) ... ;; ... esac 如果找到一个匹配,相应的序列将被执行。找到一个匹配之后,不会再尝试其后的匹配。
如果没有模式可以匹配,返回值是 0。否则,返回序列中最后执行的命令的返回值。
select 语句 select语句可以说用得很少,但其实在需要交互选择的场景下非常实用。它的完整格式是:
Copy select name [ in word ] do list done 它可以显示出带编号的菜单,用户输入不同的编号就可以选择不同的菜单,并执行不同的功能。我们看一个例子:
Copy #!/bin/bash echo "What is your favourite OS?" select name in "Linux" "Windows" "Mac OS" "UNIX" "Android" do echo "You have selected $name" done 运行结果是这样的:
Copy What is your favourite OS?
- Linux
- Windows
- Mac OS
- UNIX
- Android #? 4↙ You have selected UNIX #? 1↙ You have selected Linux #? 9↙ You have selected #? 2↙ You have selected Windows #?^D #?用来提示用户输入菜单编号,这实际是环境变量PS3的值,可以通过改这变量来改用户提示信息。^D表示按下 Ctrl+D 组合键,它的作用是结束 select 循环。
如果用户输入的菜单编号不在范围之内,例如上面我们输入的 9,那么就会给 name 赋一个空值;如果用户输入一个空值(什么也不输入,直接回车),会重新显示一遍菜单。
注意,select 是无限循环(死循环),输入空值,或者输入的值无效,都不会结束循环,只有遇到 break 语句,或者按下 Ctrl+D 组合键才能结束循环。通常和 case in 一起使用,在用户输入不同的编号时可以做出不同的反应。例如
Copy echo "What is your favourite OS?" select name in "Linux" "Windows" "Mac OS" "UNIX" "Android" do case $name in "Linux") echo "Linux是一个类UNIX操作系统,它开源免费,运行在各种服务器设备和嵌入式设备。" break ;; "Windows") echo "Windows是微软开发的个人电脑操作系统,它是闭源收费的。" break ;; ...... *) echo "输入错误,请重新输入" esac done ( list ) 语句 ( list )会让 list 序列将在一个子 shell 中执行。变量赋值和影响 shell 环境变量的内建命令在命令结束后不会再起作用。返回值是序列的返回值。
这个在需要临时切换目录或者改变环境变量的情况下非常使用。例如封装编译内核的命令,实现任何目录下都可以直接编译,我们总需要先cd到内核根目录,再make编译,最后再cd回原目录。例如:
Copy
alias mkernel='cd /linux ; make -j4 ; cd -'
这样会导致,在编译过程如果Ctrl + C取消返回时,你所处在的目录就变成了/linux。这种情况下,使用( list )就能解决这问题,甚至都不需要cd -返回原目录,直接退出即可。
Copy alias mkernel='(cd ~/linux ; make -j4)' 也例如,有某个程序比较挫,只能在程序目录执行,在其他目录,甚至上一级目录执行,都会找不到资源文件导致退出,我们可以这样解决:
Copy alias xmind='(cd ~/软件/xmind/XMind_amd64 &>/dev/null && nohup ./XMind &>/dev/null) &' (( expression)) 语句 表达式 expression 将被求值。如果表达式的值非零,返回值就是 0;否则返回值是 1。这种做法和 let "expression" 等价。
expression 语句 在 if 语句中,我们喜欢用 if [ expression ]; then ... fi的单括号的形式,但看大神们的脚本,他们更常用if expression; then ... fi的双括号形式。
[ ... ]等效于test命令,而...是另一种命令语法,相似功能却更高级,它除了传统的条件表达式(Eg. [ ${val} -eq 0 ])外,还支持表达式的转义,就是说可以像在其他语言中一样使用出现的比较符号,例如>,<=,&&,||等。
举个例子,要判断变量val有值且大于4,用单括号需要这么写:
Copy [ -n ${val} -a ${val} -gt 4 ] 用双括号可以这么写:
Copy -n ${val} && ${val} > 4 当使用==和!=操作符时,操作符右边的字符串被认为是一个模式,根据下面 模式匹配 章节中的规则进行匹配。如果匹配则返回值是 0,否则返回1。模式的任何部分可以被引用,强制使它作为一个字符串而被匹配。
引用# 这里主要讲的是$'string'特殊格式,注意的是,必须是单引号。它被扩展为string,其中的反斜杠转义字符被替换为 ANSI C 标准中规定的字符。反斜杠转义序列,如果存在的话,将做如下转换:
转义 含义 \a alert (bell) 响铃 \b backspace 回退 \e an escape character 字符 Esc \f form feed 进纸 \n new line 新行符 \r carriage return 回车 \t horizontal tab 水平跳格 \v vertical tab 竖直跳格 \ backslash 反斜杠 ' single quote 单引号 \nnn 一个八比特字符,它的值是八进制值 nnn (一到三个数字) \xHH 一个八比特字符,它的值是十六进制值 HH (一到两个十六进制数字) \cx 一个 ctrl-x 字符 例如,我希望把有换行的一段话暂存到某个变量:
Copy $ var="第一行"$'\n'"第二行" $ echo "${var}" 第一行 第二行 参数# 数组# Bash 提供了一维数组变量。任何变量都可以作为一个数组;内建命令declare可以显式地定义数组。数组的大小没有上限,也没有限制在连续对成员引用和 赋值时有什么要求。数组以整数为下标,从 0 开始。
除了```declare``定义数组外,更常用的是以下两种方式定义数组变量:
Copy $ array_var=( "mem1" 3 str ) $ array_var[4]="mem4"
$ echo
如果下标是 @ 或是 ,它扩展为数组的所有成员。 这两种下标只有在双引号中才不同。在双引号中,${name[]},把所有成员当成一个词,用特殊变量 IFS 的第一个字符分隔;${name[@]} 将数组的每个成员扩展为一个词。 如果数组没有成员,${name[@]} 扩展为空串。这种不同类似于特殊参数 * 和 @ 的扩展。在作为函数参数传递的时候能很明显感受到他们的差别。
Copy #定义数组 $ array=(a b c)
$ function func() {
echo first para is $1 echo second para is $2 echo third para is $3 }
$ func "${array[*]}" first para is a b c second para is third para is
$ func "${array[@]}" first para is a second para is b third para is c 内建命令 unset 用于销毁数组。unset name[subscript] 将销毁下标是 subscript 的元素。 unset name, 这里name 是一个数组,或者 unset name[subscript], 这里subscript 是 *或者是@,将销毁整个数组。
Copy mkdir /usr/local/src/bash/{old,new,dist} 等效于
Copy mkdir /usr/local/src/bash/old /usr/local/src/bash/new /usr/local/src/bash/dist 除此之外,还支持模式匹配来批量选择,例如:
Copy chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}} 变量扩展# 我们知道,${var}的形式可以获取变量var的值,但其实还可以有更多花式玩法。其中~表示用户根目录其实属于 波浪线扩展,这比较常见,不展开介绍了。
下面的每种情况中,word 都要经过波浪线扩展,参数扩展,命令替换和 算术扩展。如果不进行子字符串扩展,bash 测试一个没有定义或值为空的 参数;忽略冒号的结果是只测试未定义的参数。
大致描述下变量扩展的功能:
扩展 功能
有点拗口,举个例子就懂了
Copy
$ var_name=val
$ val="Bash expansion"
$ echo
!有一种例外情况,那就是${!prefix*},下面再介绍。
Copy
$ VAR_A=a $ VAR_B=b $ VAR_C=c
$ echo
${parameter:=word}和${parameter:-word}有什么差别?还是举个例子:
Copy
$ unset var
$ echo ${var}
$ echo ${var:=test} test
$ echo ${var} test
$ unset var
$ echo ${var:-test} test
$ echo ${var}
所以,差别在于,当parameter为空时,${parameter:=word}会比${parameter:-word}多做一步,就是把word的值赋给parameter。
${parameter##word} word支持模式匹配,从parameter的开始位置寻找匹配,一个#的是寻找最短匹配,两个#的是寻找最长匹配,把匹配的内容删除后,把剩下的返回。例如:
Copy
$ str="we are testing, we are testing"
$ echo
如果 parameter是一个数组变量,下标是@或者是*,模式删除将依次施用于数组中的每个成员,最后扩展为结果的列表。
${parameter%%word} 这也是在parameter中删除匹配的内容后返回。%与#非常类似,前者是从头开始匹配,后者是从尾部开始匹配。同样的,一个%是寻找最短匹配,两个%%是寻找最长匹配。例如:
Copy
$ str="we are testing, we are testing"
$ echo
如果 parameter是一个数组变量,下标是@或者是*,模式删除将依次施用于数组中的每个成员,最后扩展为结果的列表。
Copy $ str="we are testing, we are testing"
$ echo ${str/we are/I am} I am testing, we are testing
$ echo ${str//we are/I am} I am testing, I am testing
$ echo ${str/are/} we testing, we are testing
$ echo
如果 parameter是一个数组变量,下标是@或者是*,模式删除将依次施用于数组中的每个成员,最后扩展为结果的列表。
路径扩展# 我们经常会这样使用路径扩展,ls ~/work*,这里的就是路径匹配的一种,表示匹配包含空串的任何字符串。除了之外,还有?和[。路径扩展其实运用了模式匹配,所以匹配规则不妨直接看模式匹配。
模式匹配 任何模式中出现的字符,除了下面描述的特殊模式字符外,都匹配它本身。 模式中不能出现 NUL 字符。如果要匹配字面上的特殊模式字符,它必须被引用。
特殊模式字符有下述意义:
*: 匹配任何字符串包含空串。 ?: 匹配任何单个字符。 [...]: 匹配括号内的任意一个字符,与正则匹配一致。 与正则的[...]一致,[!...]或者[^...]表示不匹配括号内的字符;[a-zA-Z]表示从a到z以及从A到Z的所有字符;也支持[:alinum:]这类的特殊字符。
如果使用内建命令 shopt 启用了 shell 选项 extglob, 将识别另外几种模式匹配操作符。
?(pattern-list):匹配所给模式零次或一次出现 *(pattern-list):匹配所给模式零次或多次出现 +(pattern-list):匹配所给模式一次或多次出现 @(pattern-list):准确匹配所给模式之一 !(pattern-list):任何除了匹配所给模式之一的字串 重定向# 简单的重定向不累述了,讲一些高级用法。
Here Documents# here-document 的格式是:
Copy <<[-]word here-document delimiter 这种重定向使得 shell 从当前源文件读取输入,直到遇到仅包含 word 的一行 (并且没有尾部空白,trailing blanks) 为止。直到这一点的所有行被用作 命令的标准输入。
还是听拗口,咱们看例子:
Copy $ cat <<EOF
fist line second line third line EOF fist line second line third line 上述的做法,把两个EOF之间的内容作为一个文件,传递给cat命令。甚至,我们还有更高级的用法,实现动态创建文件。
Copy $ kernel=linux $ cat > ./readme.txt <<EOF
You are using kernel
${kernel} EOF $ cat ./readme.txt You are using kernel linux Here Strings# here-document 的变种,形式是
Copy <<<word word 被扩展,提供给命令作为标准输入,例如,我希望检索变量的值,有以下两种做法:
Copy
$ echo
这操作暂时没用过,待补充示例。
总结# 本文结合man bash以及自己的一些经验,总结了Shell编程的一些高级用法。还是那句话,建议有一定基础的同学学习,毕竟在跑之前要先学会走路不是?
分类: 编程语言-Shell