第三章:特殊字符
在脚本或其他别的地方出现的特殊字符
-
#
-
注释也可以出现在一个命令语句的后面。
1 echo "A comment will follow." # 这里可以添加注释.
2 # ^ 注意在#前面可以有空白符 #
|
注释行前面也可以有空白字符.
 |
在同一行中,命令不会跟在一个注释的后面。因为这种情况下没有办法分辨注释的结尾,命令只能放在同一行的行首。用另外的一个新行开始下一个注释。
|
 |
当然了,在echo命令给出的一个转义的#字符并不会开始一个注释。同样地,出现在一些参数代换结构和在数值常量表达式中的#字符也同样不会开始一个注释。
1 echo "The # here does not begin a comment."
2 echo 'The # here does not begin a comment.'
3 echo The \# here does not begin a comment.
4 echo The # here begins a comment.
5
6 echo ${PATH#*:} # 前面的#是参数代换,不是注释.
7 echo $(( 2#101011 )) # 基本转换,不是注释.
8
9 # 多谢, S.C.
|
标准的引用和转义 符("'\)可以转义#。 |
当然,模式匹配操作也可以使用#,而不必把它当做注释的开始。
-
;
-
1 echo hello; echo there
2
3
4 if [ -x "$filename" ]; then # 注意:"if" and "then"需要分隔符
5 # 思考一下这是为什么?
6 echo "File $filename exists."; cp $filename $filename.bak
7 else
8 echo "File $filename not found."; touch $filename
9 fi; echo "File test complete."
|
注意”;”有时需要转义.
-
;;
-
1 case "$variable" in
2 abc) echo "\$variable = abc" ;;
3 xyz) echo "\$variable = xyz" ;;
4 esac
|
-
.
-
-
.
-
作为目录名时,单个点(.)表示当前目录,两个点(..)表示上一级目录(译者注:或称为父目录)。
bash$ pwd
/home/bozo/projects
bash$ cd .
bash$ pwd
/home/bozo/projects
bash$ cd ..
bash$ pwd
/home/bozo/
|
单点(.)文件名常常被当作文件移动命令的目的路径.
bash$ cp /home/bozo/current_work/junk/* .
|
-
.
-
-
"
-
-
'
-
-
,
-
-
\
-
\X "转义"字符为X.它有"引用"X的作用,也等同于直接在单引号里的'X'.\符也可以用于引用双引号(")和单引号('),这时双引号和单引号就表示普通的字符,而不是表示引用了。
参考第五章对转义字符的更深入的解释。
-
/
-
它也是算术操作符中的除法.
-
`
-
-
:
-
死循环可以这么写:
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
|
在必须要有两元操作的地方作为一个分隔符, 参考例子 8-2和默认参数.
1 : ${username=`whoami`}
2 # ${username=`whoami`} 如果没有开头的:,将会出错
3 # 除非"username"是一个外部命令或是内建命令...
|
在here document中的一个命令作为一个分隔符. 参考例子 17-10.
在参数替换中为字符串变量赋值 (就像例子 9-14).
1 : ${HOSTNAME?} ${USER?} ${MAIL?}
2 # 如果列出的一个或多个基本的环境变量没有设置,
3 #+ 将打印出错信息。
|
变量扩展/子串代换.
和重定向操作符(>)连用, 可以把一个文件的长度截短为零,文件的权限不变。如果文件不存在,则会创建一个新文件。
1 : > data.xxx # 文件"data.xxx"现在长度为0了
2
3 # 作用相同于:cat /dev/null >data.xxx(译者注:echo >data.xxx也可以)
4 # 但是,用NULL(:)操作符不会产生一个新的进程,因为NULL操作符是内建的。
|
请参考例子 12-14.
和添加重定向操作符(>>)连用(: >> target_file).如果目标文件存在则什么也没有发生,如果目标文件不存在,则创建它。
 |
这只能应用在普通文件中,不能用在管道,符号链接和其他的特殊文件。
|
虽然这是不被推荐的,但是NULL操作符(:)也可以用于开始注释一行。使用#来注释一行将会使Bash不会检查这行后面的语法是否有错,因此#注释几乎可以出现任何的东西。但是,对于用NULL操作符(:)注释的行则不是这样。
-
下面一个会产生错误的注释。
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
|
-
!
-
在不同的环境里,感叹号也可以出现在间接变量引用.
还有一种环境里,在命令行中,感叹号(!)调用属于历史命令机制的调用(详细请看附录 J).值得一提的是,在一个脚本里,命令历史机制是被禁止的。
-
*
-
bash$ echo *
abs-book.sgml add-drive.sh agram.sh alias.sh
|
星号(*)也用于正则表达式中匹配任意数字字符。.
-
*
-
-
?
-
在双括号结构里,问号(?)表示C风格的三元操作符.请参考例子 9-30.
在参数替换表达式里,问号(?)测试一个变量是否被设置了值.
-
?
-
-
$
-
一个变量名前面加一个$字符前缀表示引用该变量的内容。
-
$
-
-
-
-
${}
-
-
$*, $@
-
-
$?
-
-
$$
-
-
()
-
 |
一组由圆括号括起来的命令是新开一个子shell来执行的.
因为是在子shell里执行,在圆括号里的变量不能被脚本的其他部分访问。因为父进程(即脚本进程)不能存取子进程(即子shell)创建的变量。(译者注:读者若对这部分内容感兴趣,可以参考stevens的<<Advance Unix Environment Programing>>一书中对进程的描述。).
1 a=123
2 ( a=321; )
3
4 echo "a = $a" # a = 123
5 # 在圆括号里的变量"a"实际上是一个局部变量,作用局域只是在圆括号内用于数组始初化
|
|
-
{xxx,yyy,zzz,...}
-
一个命令可以在文件名扩展中从逗号分隔的各模式来扩展参数列表。 [1] 文件名将会依照列表中逗号分隔开的模式匹配扩展。
 |
在扩展中的所有模式都不能包含空白字符,除非空白字符是被转义或引用的。
echo {file1,file2}\ :{\ A," B",' C'}
file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C
|
-
{}
-
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 (结果是在代码块里的值)
4
5 # 多谢, S.C.
|
由花括号括起的代码块可以引起输入输出的I/O重定向。
例子 3-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.
|
例子 3-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 # 查询该软件包能否被安装.
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手册来理解上面所用的选项。
39
40 exit 0
|
 |
不像一个用圆括号括起来的命令组,一个用花括号括起的代码块不会以一个子shell运行。[2]
|
-
{} \;
-
 |
分号";"结束find命令中-exec选项的命令序列.它应该转义一下以免被shell误解释。
|
-
[ ]
-
测试在[ ]中的表达式. 注意[是shell内建的测试的一部分(同义于测试),并非 是外部命令/usr/bin/test的链接.
-
[[ ]]
-
测试[[ ]]之中的表达式(shell的关键字).
参考[[ ... ]]结构的讨论.
-
[ ]
-
在数组的上下文中,方括号表示数组的每个元素的数字编号.
1 Array[1]=slot_1
2 echo ${Array[1]}
|
-
[ ]
-
用于正则表达式的一部分,方括号描述一个匹配的字符集范围.
-
(( ))
-
扩展并计算(( ))里的整数表达式[译者注:粗心的读者要注意了,是整数计算,可不能用来做浮点计算].
参考(( ... ))结构的讨论.
-
> &> >& >> <
-
scriptname >filename重定向scriptname的输出到文件filename中去. 如果文件filename存在则将会被覆盖.
command &>filename 会重定向命令command标准输出(stdout)和标准错误(stderr)到文件filename中.
command >&2 把命令command的标准输出(stdout)重定向到标准错误(stderr).
scriptname >>filename appends把脚本scriptname的输出追加到文件filename.如果filename不存在,则它会被创建.
(command)>
<(command)
在不同的上下文中, 字符 "<"和">"会被当作字符比较操作符.
在另一种不同的上下文中, 字符"<"和">"被当作整数比较操作符. 请参考例子 12-9.
-
<<
-
-
<<<
-
-
<, >
-
-
\<, \>
-
bash$ grep '\<the\>' textfile
-
|
-
1 echo ls -l | sh
2 # 把"echo ls -l"的输出传给shell,
3 #+ 这等同与直接的"ls -l".
4
5
6 cat *.lst | sort | uniq
7 # 合并且排序所有的".lst"文件,然后删除多余的相同行.
|
一个命令或一组命令的输出可以由管道传给一个脚本.
1 #!/bin/bash
2 # uppercase.sh : 把输入字符改为大写.
3
4 tr 'a-z' 'A-Z'
5 # 字母的范围一定要引号引起来,
6 #+ 这样才能保护文件名而不会被扩展成单个字母的文件名.
7
8 exit 0
|
现在,让我们把ls -l的输出用管道与这个脚本连起来.
bash$ ls -l | ./uppercase.sh
-RW-RW-R-- 1 BOZO BOZO 109 APR 7 19:49 1.TXT
-RW-RW-R-- 1 BOZO BOZO 109 APR 14 16:48 2.TXT
-RW-R--R-- 1 BOZO BOZO 725 APR 20 20:56 DATA-FILE
|
 |
管道里的每一个进程的标准输出都被当成下一个命令的标准输入. 如果不是这种情况,那么数据流会阻塞,并且管道不会引起预期的效果。
1 cat file1 file2 | ls -l | sort
2 # 来自"cat file1 file2"的输出会消失.
|
管道以子进程来运行, 因此不能引起变量的改变。
1 variable="initial_value"
2 echo "new_value" | read variable
3 echo "variable = $variable" # variable = initial_value
|
如果在管道中的一个命令失败了,会过早的终结整个管道的执行。这称为管道破坏(broken pipe),这时会发送一个叫SIGPIPE 的信号.
|
-
>|
-
-
||
-
-
&
-
bash$ sleep 10 &
[1] 850
[1]+ Done sleep 10
|
在一个脚本里,在后台运行的命令或是偶数的循环可以在后台运行.
例子 3-3. 在后台运行一个循环
1 #!/bin/bash
2 # background-loop.sh
3
4 for i in 1 2 3 4 5 6 7 8 9 10 # 第一个循环.
5 do
6 echo -n "$i "
7 done & # 把这个循环放到后台去.
8 # 它有时会后于第二个循环执行.
9
10 echo # 这个'echo'有时不会打印出来.
11
12 for i in 11 12 13 14 15 16 17 18 19 20 # 第二个循环.
13 do
14 echo -n "$i "
15 done
16
17 echo # 这个'echo'有时不会打印出来.
18
19 # ======================================================
20
21 # 这个脚本的输出是:
22 # 1 2 3 4 5 6 7 8 9 10
23 # 11 12 13 14 15 16 17 18 19 20
24
25 # 然而有时你也有可能得到如下的输出:
26 # 11 12 13 14 15 16 17 18 19 20
27 # 1 2 3 4 5 6 7 8 9 10 bozo $
28 # (第二个 'echo'没有执行. 为什么?)
29
30 # 偶尔也会:
31 # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
32 # (第一个 'echo' 没有执行. 为什么?)
33
34 # 非常罕有的情况可能是:
35 # 11 12 13 1 2 3 4 5 6 7 8 9 10 14 15 16 17 18 19 20
36 # 前台的循环抢占了后台的循环输出.
37
38 exit 0
39
40 # Nasimuddin Ansari 建议在第6行和第14行的:echo -n "$i" 加入sleep 1
41 #+ 将会更有趣
42 #
|
 |
脚本中在一条在后台运行的命令可能会引起脚本悬挂,等待一个击键动作。幸运的是,有一个补救的办法.
|
-
&&
-
-
-
-
COMMAND -[选项1][选项2][...]
ls -al
sort -dfu $filename
set -- $variable
1 if [ $file1 -ot $file2 ]
2 then
3 echo "File $file1 is older than $file2."
4 fi
5
6 if [ "$a" -eq "$b" ]
7 then
8 echo "$a is equal to $b."
9 fi
10
11 if [ "$c" -eq 24 -a "$d" -eq 47 ]
12 then
13 echo "$c equals 24 and $d equals 47."
14 fi
|
-
-
-
1 (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
2 # 把整个目录树从一个目录移到另外一个目录
3 # [谦逊的 Alan Cox <a.cox@swansea.ac.uk>,作了一些修改]
4
5 # 1) cd /source/directory 源目录,这是要移动的目录所在地.
6 # 2) && 逻辑与: 如果'cd'命令操作成功,然后就执行下一条命令.
7 # 3) tar cf - . tar命令的'c'选项创建一个新的归档文件,
8 # 而'f'(file)选项,后跟一个'-'表示创建的目标文件是标准输出,
9 # 并且要操作的源目录是当前目录 ('.').
10 # 4) | 然后由管道输出...
11 # 5) ( ... ) 一个子shell
12 # 6) cd /dest/directory 将当前目录切换到目的目录.
13 # 7) && 逻辑与,和上面的解释一样
14 # 8) tar xpvf - 解开归档文件('x'),保持文件属主和文件的权限('p'),
15 # 并且把输出的详细信息打印到标准输出 ('v'),
16 # 从标准输入读('f'后跟'-').
17 #
18 # 注意'x'是一个命令,而'p', 'v', 'f'是选项.
19 # 哇!
20
21
22
23 # 更优雅的,但作用一样的:
24 # cd source/directory
25 # tar cf - . | (cd ../dest/directory; tar xpvf -)
26 #
27 # 也可以这样:
28 # cp -a /source/directory/* /dest/directory
29 # 或:
30 # cp -a /source/directory/* /source/directory/.[^.]* /dest/directory
31 # 如果在/source/directory有隐藏文件.
|
1 bunzip2 linux-2.6.13.tar.bz2 | tar xvf -
2 # -- 解压tar文件 -- | --然后把结果传给"tar"--
3 # 如果"tar"没有打上处理"bunzip2"程序的补丁,
4 # 就需要用管道连接两个不连续的步骤.
5 # 这个练习的目的是解压内核源码包.
|
注意"-"环境不是一个Bash操作符提供的,而是被由一些写标准输出的UNIX软件包来验证的,比如tar, cat,等等.
bash$ echo "whatever" | cat -
whatever
|
当希望提供一个文件名时, '-' 重定向输出到标准输出(有时像tar cf),或者从标准输入接受输入,就好像它们是一个文件一样 . 这是在管道中使用文件导向(file-oriented)软件包作为一个过滤器的方法.
bash$ file
Usage: file [-bciknvzL] [-f namefile] [-m magicfiles] file...
|
只在命令行单独给出命令file,会引起一个错误信息.
加一个"-"来看看结果,这会使shell等候用户的输入。
bash$ file -
abc
standard input: ASCII text
bash$ file -
#!/bin/bash
standard input: Bourne-Again shell script text executable
|
现在命令从标准输入接受输入并分析它.
"-"能被用来把标准输出由管道输出到其他的命令.这样就允许使用在文件开头增加几行的技巧.
使用 diff 来比较一个文件和另一个文件的某一段:
grep Linux file1 | diff file2 -
最后Finally,再展示一个使用- 的tar命令的真实例子.
例子 3-4. 备份前24小时被修改的文件
1 #!/bin/bash
2
3 # 备份当前目录下所有前24小时被修改的文件为一个归档压缩包(归档并且压缩)
4 #
5
6 BACKUPFILE=backup-$(date +%m-%d-%Y)
7 # 在备份文件中嵌入日期.
8 # 多谢Joshua Tschida的这个主意.
9 archive=${1:-$BACKUPFILE}
10 # 如果没有在命令行上指定备份的归档文件名,
11 #+ 会以"backup-MM-DD-YYYY.tar.gz."作为默认的文件名
12
13 tar cvf - `find . -mtime -1 -type f -print` > $archive.tar
14 gzip $archive.tar
15 echo "Directory $PWD backed up in archive file \"$archive.tar.gz\"."
16
17
18 # Stephane Chazelas指出:如果有许多文件被找到
19 #+ 或任何一个文件名中包含有空白字符
20 #+ 上面的代码将会失败.
21
22 # 他建议用下面的代码:
23 # -------------------------------------------------------------------
24 # find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$archive.tar"
25 # using the GNU version of "find".
26
27
28 # find . -mtime -1 -type f -exec tar rvf "$archive.tar" '{}' \;
29 # portable to other UNIX flavors, but much slower.
30 # -------------------------------------------------------------------
31
32
33 exit 0
|
 |
以"-"字符开头为文件名的文件当加上"-"的定向操作符时可能会引起问题.脚本应该检查这种情况并且给这种文件增加合适的路径前缀,例如 ./-FILENAME, $PWD/-FILENAME, 或$PATHNAME/-FILENAME.
如果一个变量的值以-开头,同样也可能会产生问题.
1 var="-n"
2 echo $var
3 # 和"echo -n"一样,什么也不会输出.
|
|
-
-
-
 |
不要弄混了这儿使用的"-"和上面刚讨论的"-"重定向操作符.对于"-"字符的解释应依赖于它出现的环境.
|
-
-
-
-
=
-
在不同的上下文, "="是一个字符串比较操作符.
-
+
-
在不同的上下文中, +是一个正则表达式操作符.
-
+
-
一些命令和内建命令 用+来启用一些选项,用-来禁用它们.
-
%
-
在不同的上下文中,%是一个模式匹配 操作符.
-
~
-
-
~+
-
-
~-
-
-
=~
-
-
^
-
-
控制字符
-
在脚本文件中控制字符是不起作用的.
-
Ctl-B
退格 (非破坏性的).
-
Ctl-C
中断. 终结一个前台作业.
-
Ctl-D
从一个shell中退出 (类似于exit).
"EOF" (文件结尾:end of file).它也用于表示标准输入(stdin)的结束.
在控制台或xterm 窗口输入文本时, Ctl-D删除在光标下的字符.如果没有字符存在,Ctl-D 则会登录出该会话. 在一个xterm窗口中,则会产生关闭此窗口的效果。
-
Ctl-G
"哔" (beep).在一些老式的打字机终端上,它会响一下铃.
-
Ctl-H
"杀掉" (破坏性的退格). 删除光标前的一个字符===.
1 #!/bin/bash
2 # 在一个字符串里嵌入 Ctl-H.
3
4 a="^H^H" # 两个 Ctl-H (退格).
5 echo "abcdef" # abcdef
6 echo -n "abcdef$a " # abcd f
7 #以一个空格结尾 ^ ^ 退二格.
8 echo -n "abcdef$a" # abcdef
9 # 现在没有尾部的空格 不退格了 (为什么?).
10 # 结果和预料的不一样.
11 echo; echo
|
-
Ctl-I
水平制表符.
-
Ctl-J
新行(换一行并到行首).
-
Ctl-K
垂直制表符.
在控制台或xterm 窗口输入文本时, Ctl-K 会删除从光标所在处到行尾的所有字符。
-
Ctl-L
清屏 (重绘屏幕,清除前面的打印信息).这与clear命令作用相同.
-
Ctl-M
回车.
1 #!/bin/bash
2 # 多谢Lee Maschmeyer的例子.
3
4 read -n 1 -s -p $'Control-M leaves cursor at beginning of this line. Press Enter. \x0d'
5 # 是的, '0d'是Control-M的十六进制值.
6 echo >&2 # '-s'使所有被键入的字符都不回显,
7 #+ 所以需要明确地键入新行.
8
9 read -n 1 -s -p $'Control-J leaves cursor on next line. \x0a'
10 echo >&2 # Control-J 是换行.
11
12 ###
13
14 read -n 1 -s -p $'And Control-K\x0bgoes straight down.'
15 echo >&2 # Control-K 是垂直制表符.
16
17 # 展示垂直制表符作用的更好的例子是:
18
19 var=$'\x0aThis is the bottom line\x0bThis is the top line\x0a'
20 echo "$var"
21 # 这和上面的例子一样工作.但是:
22 echo "$var" | col
23 # 这使行的右端比左端更高.
24 # 这也解释了为什么我们以一个换行符开始和结束 --
25 #+ 是为了避免屏幕显示混乱.
26
27 # 这是Lee Maschmeyer的解释:
28 # --------------------------
29 # 在第一个垂直制表符例子中 . . . 垂直制表符使还未打印回车就直接垂直打印下来。
30 #
31 # 这只在不能“倒后”的设备里才成立,比如在Linux控制台,
32 #
33 # 垂直制表符真正的意图是能垂直地往上移,而不是往下移.
34 # 可以在打印机里用于打印上标.
35 # 这个要点的作用被用于仿效垂直制表符正确的功能.
36
37 exit 0
|
-
Ctl-Q
解冻 (XON).
它解冻终端的标准输入.
-
Ctl-S
挂起输入 (XOFF).
它冻结终端的标准输入. (用 Ctl-Q 可恢复输入.)
-
Ctl-U
删除从光标到行首的一行输入.在某些设置里,Ctl-U 删除整行的输入,而不管光标的位置.
-
Ctl-V
当输入一个文本, Ctl-V允许插入控制字符。例如,下面两个命令是相等的:
1 echo -e '\x0a'
2 echo <Ctl-V><Ctl-J>
|
Ctl-V 主要用于文本编辑.
-
Ctl-W
当在控制台或一个xterm窗口敲入文本时, Ctl-W 会删除从在光标处往后的第一个空白符之间的内容.在某些设置里, Ctl-W 删除光标往后到第一个非文字和数字之间的字符.
-
Ctl-Z
暂停一个前台作业.
-
空白
-
空行不会影响脚本的行为,因此使用它可以很好的划分独立的函数段以增加可读性。
特殊变量$IFS用来分隔一些输入命令的分隔符,默认是空白符。
为了在字符串或在变量中产生空白,应该使用引用.
|