分类:
2010-01-03 22:24:00
摘自《高级bash脚本编程指南》
人们常说,书是由厚读到薄,再由薄读到厚。俺没有那本事,但bash的特殊字符众多,多了也就记不住,故把那些特殊中的“特殊”(对我而言)给摘录并整理一下,聊以方便记忆。
“
部分引用[双引号, 即"]. “STRING“将会阻止(解释)STRING中大部分特殊的字符.
‘
全引用[单引号, 即']. ‘STRING‘将会阻止STRING中所有特殊字符的解释. 这是一种比使用“更强烈的形式.
,
逗号操作符. 逗号操作符链接了一系列的算术操作. 虽然里边所有的内容都被运行了,但只有最后一项被返回.
1 let "t2 = ((a = 9, 15 / 3))" # Set "a = 9" and "t2 = 15 / 3"
`
命令替换. `command`结构可以将命令的输出赋值到一个变量中去. `也叫后置引用(backquotes)
关于引用
引用的字面意思就是将字符串用双引号括起来. 它的作用就是保护字符串中的特殊字符不被shell或者shell脚本重新解释, 或者扩展. (我们这里所说的”特殊”指的是一些字符在shell中具有的特殊意义, 而不是字符的字面意思, 比如通配符 — *.)
bash$ ls -l [Vv]* -rw-rw-r-- 1 bozo bozo 324 Apr 2 15:05 VIEWDATA.BAT -rw-rw-r-- 1 bozo bozo 507 May 4 14:25 vartrace.sh -rw-rw-r-- 1 bozo bozo 539 Apr 14 17:11 viewdata.sh bash$ ls -l '[Vv]*' ls: [Vv]*: No such file or directory
在日常的演讲和写作中, 当我们”引用”一个短语的时候, 这意味着这个短语被区分以示它有特别的含义. 但是在Bash脚本中, 当我们引用一个字符串的时候, 我们区分这个字符串是为了保护它的字面含义.
某些程序和工具能够重新解释或者扩展被引用的特殊字符. 引用的一个重要作用就是保护命令行参数不被shell解释, 但是还是能够让正在调用的程序来扩展它.
bash$ grep '[Ff]irst' *.txt file1.txt:This is the first line of file1.txt. file2.txt:This is the First line of file2.txt.
注意一下未引用的 grep [Ff]irst *.txt 在Bash shell下的行为. (除非正好当前工作目录下有一个名字为 first 或 First 的文件. 然而这是引用的另一个原因)
!
取反操作符[叹号, 即!]. ! 操作符将会反转命令的退出码的结果, 也会反转测试操作符的意义, 比如修改”等号”( = )为”不等号”( != ). ! 操作符是Bash的关键字.
在一个不同的上下文中, ! 也会出现在变量的间接引用中.
在另一种上下文中, 如命令行模式下, ! 还能反转bash的历史机制 (例如在命令行中执行 $ echo “hello!” 会产生 bash: !”: event not found 的错误). 需要注意的是, 在一个脚本中, 历史机制是被禁用的.
:
空命令[冒号, 即:]. 等价于”NOP” (no op, 一个什么也不干的命令). 也可以被认为与shell的内建命令true作用相同. “:“命令是一个bash的内建命令, 它的退出码(exit status)是”true”(0).
1 : 2 echo $? # 0
死循环:
1 while : 2 do 3 operation-1 4 operation-2 5 ... 6 operation-n 7 done 8 9 # 与下边相同: 10 # while true 11 # do 12 # ... 13 # done
在if/then中的占位符:
1 if condition 2 then : # 什么都不做,引出分支. 3 else 4 take-some-action 5 fi
在一个二元命令中提供一个占位符和默认参数.
1 : ${username=`whoami`} 2 # ${username=`whoami`} 如果没有开头的":"的话, 将会给出一个错误, 3 # 除非"username"是一个命令或者内建命令...
在here document中提供一个命令所需的占位符.
使用参数替换来评估字符串变量
1 : ${HOSTNAME?} ${USER?} ${MAIL?} 2 # 如果一个或多个必要的环境变量没被设置的话, 3 #+ 就打印错误信息.
在与 > 重定向操作符结合使用时, 将会把一个文件清空, 但是并不会修改这个文件的权限. 如果之前这个文件并不存在, 那么就创建这个文件.
1 : > data.xxx # 文件"data.xxx"现在被清空了. 2 3 # 与 cat /dev/null >data.xxx 的作用相同 4 # 然而,这并不会产生一个新的进程, 因为":"是一个内建命令.
在与 >> 重定向操作符结合使用时, 将不会对预先存在的目标文件(: >> target_file)产生任何影响. 如果这个文件之前并不存在, 那么就创建它.
注:这只适用于正规文件, 而不适用于管道, 符号连接, 和某些特殊文件.
也可能用来作为注释行, 虽然我们不推荐这么做. 使用#来注释的话, 将关闭剩余行的错误检查, 所以可以在注释行中写任何东西. 然而, 使用 : 的话将不会这样.
1 : This is a comment that generates an error, ( if [ $x -eq 3] ).
“:“还用来在/etc/passwd和$PATH变量中做分隔符.
bash$ echo $PATH /usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr/games
()
命令组.
1 (a=hello; echo $a)
在括号中的命令列表, 将会作为一个子shell来运行.
在括号中的变量,由于是在子shell中,所以对于脚本剩下的部分是不可用的. 父进程, 也就是脚本本身, 将不能够读取在子进程中创建的变量, 也就是在子shell中创建的变量.
1 a=123 2 ( a=321; ) 3 4 echo "a = $a" # a = 123 5 # 在圆括号中a变量, 更像是一个局部变量.
初始化数组.
1 Array=(element1 element2 element3)
(( ))
整数扩展.
扩展并计算在(( ))中的整数表达式. 与 let 命令很相似, ((…))结构允许算术扩展和赋值. 举个简单的例子, a=$(( 5 + 3 )), 将把变量”a”设为”5 + 3″, 或者8. 然而, 双圆括号结构也被认为是在Bash中使用C语言风格(赋值号“=”两边允许有空格)变量操作的一种处理机制.
{xxx,yyy,zzz,…}
大括号扩展.
1 cat {file1,file2,file3} > combined_file 2 # 把file1, file2, file3连接在一起, 并且重定向到combined_file中. 3 cp file22.{txt,backup} 4 # 拷贝"file22.txt"到"file22.backup"中
一个命令可能会对大括号中的以逗号分割的文件列表起作用. (通配(globbing))将对大括号中的文件名做扩展.
在大括号中, 不允许有空白, 除非这个空白被引用或转义.
$ echo {file1,file2}\ :{\ A," B",' C'} file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C $ echo {A,B,C}{1..5} A1 A2 A3 A4 A5 B1 B2 B3 B4 B5 C1 C2 C3 C4 C5 $ echo {A,B,C}{5..1} A5 A4 A3 A2 A1 B5 B4 B3 B2 B1 C5 C4 C3 C2 C1
注:通过上面的两个例子,相信大家也可以举一反三了。多个”{}”的串联,其里面以逗号分隔的字符串列表是按照它们给定的位置顺序扩展并组合成最终的字符串列表(文件列表),第二个例子中的“..”是“范围”操作符(借用 perl 语言中的解释,不过在 perl 语言中,“..”只表递增,而这里既可以表递增也可以表递减,默认步长为 1)。
{}
代码块[大括号, 即{}]. 又被称为内部组, 这个结构事实上创建了一个匿名函数(一个没有名字的函数). 然而, 与”标准”函数不同的是, 在其中声明的变量,对于脚本其他部分的代码来说还是可见的.
bash$ { local a; a=123; } bash: local: can only be used in a function
1 a=123 2 { a=321; } 3 echo "a = $a" # a = 321 (说明在代码块中对变量a所作的修改, 影响了外边的变量) 4 5 # 感谢, S.C.
注:上面的代码中,“{” 的后面以及 “}” 的前面必须有一个空格,而且在”{}”中的语句必须与 “;” 号结束。除非 “{“、”}”和语句不在同一行上。
下边的代码展示了在大括号结构中代码的I/O 重定向.
例子 1. 代码块和I/O重定向
1 #!/bin/bash 2 # 从/etc/fstab中读行. 3 4 File=/etc/fstab 5 6 { 7 read line1 8 read line2 9 } < $File 10 11 echo "First line in $File is:" 12 echo "$line1" 13 echo 14 echo "Second line in $File is:" 15 echo "$line2" 16 17 exit 0 18 19 # 现在, 你怎么分析每行的分割域? 20 # 小提示: 使用awk.
例子 2. 将一个代码块的结果保存到文件
1 #!/bin/bash 2 # rpm-check.sh 3 4 # 这个脚本的目的是为了描述, 列表, 和确定是否可以安装一个rpm包. 5 # 在一个文件中保存输出. 6 # 7 # 这个脚本使用一个代码块来展示. 8 9 SUCCESS=0 10 E_NOARGS=65 11 12 if [ -z "$1" ] 13 then 14 echo "Usage: `basename $0` rpm-file" 15 exit $E_NOARGS 16 fi 17 18 { 19 echo 20 echo "Archive Description:" 21 rpm -qpi $1 # 查询说明. 22 echo 23 echo "Archive Listing:" 24 rpm -qpl $1 # 查询列表. 25 echo 26 rpm -i --test $1 # 查询rpm包是否可以被安装. 27 if [ "$?" -eq $SUCCESS ] 28 then 29 echo "$1 can be installed." 30 else 31 echo "$1 cannot be installed." 32 fi 33 echo 34 } > "$1.test" # 把代码块中的所有输出都重定向到文件中. 35 36 echo "Results of rpm test in file $1.test" 37 38 # 查看rpm的man页来查看rpm的选项. 39 40 exit 0
与上面所讲到的 () 中的命令组不同的是, {大括号} 中的代码块将不会开启一个新的子shell. 有一个例外: 在pipe中的一个大括号中的代码段可能运行在一个 子shell中.
1 ls | { read firstline; read secondline; } 2 # 错误. 在大括号中的代码段, 将运行到子shell中, 3 #+ 所以"ls"的输出将不能传递到代码块中. 4 echo "First line is $firstline; second line is $secondline" # 不能工作. 5 6 # 感谢, S.C.
{} \;
路径名. 一般都在find命令中使用. 这不是一个shell内建命令.
“;“用来结束find命令序列的-exec选项. 它需要被保护以防止被shell所解释.
还有更多的特殊符号就不一一记在这里了,详细请参考《高级bash脚本编程指南》