魅力博客

魅力Linux|魅力空间|魅力博客|学习Linux|ubuntu日记|电脑教程|手机软件

深入学习shell脚本艺术-高级Bash脚本编程指南2



第二部分    基本
++++++++++++++++

第3章    特殊字符
================

#        注释,行首以#开头为注释(#!是个例外).

        1 # This line is a comment.

        注释也可以存在于本行命令的后边.

        1 echo "A comment will follow." # 注释在这里
        2 #                            ^ 注意#前边的空白

        注释也可以在本行空白的后边.

        1     # A tab precedes this comment.

        注意:命令是不能跟在同一行上注释的后边的,没有办法,在同一行上,注释的后边想
        要再使用命令,只能另起一行.
        当然,在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 # 这里开始一个注释
        5
        6 echo ${PATH#*:}       # 参数替换,不是一个注释
        7 echo $(( 2#101011 ))  # 数制转换,不是一个注释
        8
        9 # Thanks, S.C.

        标准的引用和转义字符("'\)可以用来转义#

;        命令分隔符,可以用来在一行中来写多个命令.

        1 echo hello; echo there
        2
        3
        4 if [ -x "$filename" ]; then    # 注意:"if"和"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."

        有时候需要转义

;;        终止"case"选项.

        1 case "$variable" in
        2 abc)  echo "\$variable = abc" ;;
        3 xyz)  echo "\$variable = xyz" ;;
        4 esac

.        .命令等价于source命令(见Example 11-20).这是一个bash的内建命令.

.        .作为文件名的一部分.如果作为文件名的前缀的话,那么这个文件将成为隐藏文件.
        将不被ls命令列出.

        bash$ touch .hidden-file
        bash$ ls -l          
        total 10
        -rw-r--r--    1 bozo      4034 Jul 18 22:04 data1.addressbook
        -rw-r--r--    1 bozo      4602 May 25 13:58 data1.addressbook.bak
        -rw-r--r--    1 bozo       877 Dec 17  2000 employment.addressbook
        
        
        bash$ ls -al          
        total 14
        drwxrwxr-x    2 bozo  bozo      1024 Aug 29 20:54 ./
        drwx------   52 bozo  bozo      3072 Aug 29 20:51 ../
        -rw-r--r--    1 bozo  bozo      4034 Jul 18 22:04 data1.addressbook
        -rw-r--r--    1 bozo  bozo      4602 May 25 13:58 data1.addressbook.bak
        -rw-r--r--    1 bozo  bozo       877 Dec 17  2000 employment.addressbook
        -rw-rw-r--    1 bozo  bozo         0 Aug 29 20:54 .hidden-file

        .命令如果作为目录名的一部分的话,那么.表达的是当前目录.".."表示上一级目录.

        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/* .

.        .字符匹配,这是作为正则表达是的一部分,用来匹配任何的单个字符.

"        部分引用."STRING"阻止了一部分特殊字符,具体见第5章.

'        全引用. 'STRING' 阻止了全部特殊字符,具体见第5章.

,        逗号链接了一系列的算术操作,虽然里边所有的内容都被运行了,但只有最后一项被
        返回.

        如:
        1 let "t2 = ((a = 9, 15 / 3))"  # Set "a = 9" and "t2 = 15 / 3"

\        转义字符,如\X等价于"X"或'X',具体见第5章.

/        文件名路径分隔符.或用来做除法操作.

`        后置引用,命令替换,具体见第14章

:        空命令,等价于"NOP"(no op,一个什么也不干的命令).也可以被认为与shell的内建命令(true)作用相同.":"命令是一
        个bash的内建命令,它的返回值为0,就是shell返回的true.

        如:
        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

        在一个2元命令中提供一个占位符,具体见Example 8-2,和"默认参数".如:
        1 : ${username=`whoami`}
        2 # ${username=`whoami`}   如果没有":"的话,将给出一个错误,除非"username"是
        3 #                        个命令
        在here document中提供一个占位符,见Example 17-10.

        使用"参数替换"来评估字符串变量(见Example 9-14).如:
        1 : ${HOSTNAME?} ${USER?} ${MAIL?}
        2 #     如果一个或多个必要的环境变量没被设置的话,
        3 #+ 就打印错误信息.

        "变量扩展/子串替换"
        在和 > (重定向操作符)结合使用时,把一个文件截断到0长度,没有修改它的权限.
        如果文件在之前并不存在,那么就创建它.如:
        1 : > data.xxx        #文件"data.xxx"现在被清空了.
        2
        3 #与 cat /dev/null >data.xxx 的作用相同
        4 #然而,这不会产生一个新的进程,因为":"是一个内建命令.
        具体参见Example 12-14.

        在和>>重定向操作符结合使用时,将不会对想要附加的文件产生任何影响.
        如果文件不存在,将创建.
        注意: 这只适用于正规文件,而不是管道,符号连接,和某些特殊文件.

        也可能用来作为注释行,虽然我们不推荐这么做.使用#来注释的话,将关闭剩余行的
        错误检查,所以可以在注释行中写任何东西.然而,使用:的话将不会这样.如:
        1 : This is a comment thar generates an error,(if [ $x -eq 3] ).

        ":"还用来在/etc/passwd和$PATH变量中用来做分隔符.
        bash$    echo $PATH
        /usr/local/bin:/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr/games
!        取反操作符,将反转"退出状态"结果,(见Example 6-2).也会反转test操作符的意义.比
        如修改=为!=.!操作是Bash的一个关键字.

        在一个不同的上下文中,!也会出现在"间接变量引用"见Example 9-22.

        在另一种上下文中,!还能反转bash的"history mechanism"(见附录J 历史命令)
        需要注意的是,在一个脚本中,"history mechanism"是被禁用的.

*        万能匹配字符,用于文件名匹配(这个东西有个专有名词叫file globbing),或者是正则
        表达式中.注意:在正则表达式匹配中的作用和在文件名匹配中的作用是不同的.
        bash$ echo *
        abs-book.sgml add-drive.sh agram.sh alias.sh
*        数学乘法.
        **是幂运算.
?        测试操作.在一个确定的表达式中,用?来测试结果.
        (())结构可以用来做数学计算或者是写c代码,那?就是c语言的3元操作符的
        一个.
        在"参数替换"中,?测试一个变量是否被set了.
?        在file globbing中和在正则表达式中一样匹配任意的单个字符.

$        变量替换
        1 var1=5
        2 var2=23skidoo
        3
        4 echo $var1     # 5
        5 echo $var2     # 23skidoo
$        在正则表达式中作为行结束符.
${}        参数替换,见9.3节.
$*,$@    位置参数
$?        退出状态变量.$?保存一个命令/一个函数或者脚本本身的退出状态.
$$        进程ID变量.这个$$变量保存运行脚本进程ID
()        命令组.如:
        1 (a=hello;echo $a)
        注意:在()中的命令列表,将作为一个子shell来运行.
        在()中的变量,由于是在子shell中,所以对于脚本剩下的部分是不可用的.
        如:
        1 a=123
        2 ( a=321; )          
        3
        4 echo "a = $a"   # a = 123
        5 # 在圆括号中a变量,更像是一个局部变量.

        用在数组初始化,如:
        1 Array=(element1,element2,element3)

{xxx,yyy,zzz...}
        大括号扩展,如:
        1 cat {file1,file2,file3} > combined_file
        2 # 把file1,file2,file3连接在一起,并且重定向到combined_file中.
        3
        4
        5 cp file22.{txt,backup}
        6 # 拷贝"file22.txt" 到"file22.backup"中

        一个命令可能会对大括号中的以逗号分割的文件列表起作用[1]. file globbing将对
        大括号中的文件名作扩展.
        注意: 在大括号中,不允许有空白,除非这个空白是有意义的.
        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申请的变量只能够用在函数中.

        1 a=123
        2 { a=321; }
        3 echo "a = $a"   # a = 321   (说明在代码块中对变量a所作的修改,影响了外边的变量a)
        4
        5 # Thanks, S.C.

        下边的代码展示了在{}结构中代码的I/O重定向.

Example 3-1. 代码块和I/O重定向
################################Start Script#######################################
 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.
################################End Script#########################################

Example 3-2. 将一个代码块的结果保存到文件
################################Start Script#######################################
 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
################################End Script#########################################
        注意: 与()中的命令不同的是,{}中的代码块将不能正常地开启一个新shell.[2]

{} \;    路径名.一般都在find命令中使用.这不是一个shell内建命令.
        注意: ";"用来结束find命令序列的-exec选项.

[]        test.
        test的表达式将在[]中.
        值得注意的是[是shell内建test命令的一部分,并不是/usr/bin/test中的扩展命令
        的一个连接.

[[]]    test.
        test表达式放在[[]]中.(shell关键字)
        具体查看[[]]结构的讨论.

[]        数组元素
        Array[1]=slot_1
        echo ${Array[1]}

[]        字符范围
        在正则表达式中使用,作为字符匹配的一个范围

(())    数学计算的扩展
        在(())结构中可以使用一些数字计算.
        具体参阅((...))结构.

>&>>&>><
        重定向.
        scriptname >filename 重定向脚本的输出到文件中.覆盖文件原有内容.
        command &>filename 重定向stdout和stderr到文件中
        command >&2    重定向command的stdout到stderr
        scriptname >>filename 重定向脚本的输出到文件中.添加到文件尾端,如果没有文件,
        则创建这个文件.

        进程替换,具体见"进程替换部分",跟命令替换极其类似.
        (command)>
        <(command)

        <和> 可用来做字符串比较
        <和> 可用在数学计算比较

<<        重定向,用在"here document"

<<<        重定向,用在"here string"

<,>        ASCII比较
         1 veg1=carrots
         2 veg2=tomatoes
         3
         4 if [[ "$veg1" < "$veg2" ]]
         5 then
         6   echo "Although $veg1 precede $veg2 in the dictionary,"
         7   echo "this implies nothing about my culinary preferences."
         8 else
         9   echo "What kind of dictionary are you using, anyhow?"
        10 fi

\<,\>    正则表达式中的单词边界.如:
        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"文件,然后删除所有重复的行.
        
        管道是进程间通讯的一个典型办法,将一个进程的stdout放到另一个进程的stdin中.
        标准的方法是将一个一般命令的输出,比如cat或echo,传递到一个过滤命令中(在这个
        过滤命令中将处理输入),得到结果,如:
        cat $filename1 | $filename2 | grep $search_word

        当然输出的命令也可以传递到脚本中.如:
################################Start Script#######################################
1 #!/bin/bash
2 # uppercase.sh : 修改输出,全部转换为大写
3
4 tr 'a-z' 'A-Z'
5 #  字符范围必须被""引用起来
6 #+ 来阻止产生单字符的文件名.
7
8 exit 0
################################End Script#########################################
        
        现在让我们输送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

        注意:管道中的一个进程的stdout必须被下一个进程作为stdin读入.否则,数据流会阻
        塞,并且管道将产生非预期的行为.
        如:
        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
        如果管道中的某个命令产生了一个异常,并中途失败,那么这个管道将过早的终止.
        这种行为被叫做a broken pipe,并且这种状态下将发送一个SIGPIPE信号.

>|        强制重定向(即使设置了noclobber选项--就是-C选项).这将强制的覆盖一个现存文件.

||        或-逻辑操作.

&        后台运行命令.一个命令后边跟一个&,将表示在后台运行.
        bash$sleep 10 &
        [1] 850
        [1]+    Done            sleep 10
        在一个脚本中,命令和循环都可能运行在后台.

Example 3-3. 在后台运行一个循环
################################Start Script#######################################
 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                                #在第2个循环之后,将在某些时候执行.
 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 #(第2个'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 #(第1个'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 建议加一句 sleep 1
41 #+    在 6行和14行的 echo -n "$i"之后加
42 #+    将看到一些乐趣
################################End Script#########################################
        注意:在一个脚本内后台运行一个命令,有可能造成这个脚本的挂起,等待一个按键
            响应.幸运的是,我们可以在Example 11-24附近,看到这个问题的解决办法.

&&        与-逻辑操作.

-        选项,前缀.在所有的命令内如果想使用选项参数的话,前边都要加上"-".

        COMMAND -[Option1][Option2][...]
        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

-        用于重定向 stdin 或 stdout.

################################Start Script#######################################
 1 (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
 2 # 从一个目录移动整个目录树到另一个目录
 3 # [courtesy Alan Cox <a.cox@swansea.ac.uk>, with a minor change]
 4
 5 # 1) cd /source/directory    源目录
 6 # 2) &&                      与操作,如果cd命令成功了,那么就执行下边的命令
 7 # 3) tar cf - .              'c'创建一个新文档,'f'后边跟'-'指定目标文件作为stdout
 8 #                            '-'后边的'f'(file)选项,指明作为stdout的目标文件.
 9 #                            并且在当前目录('.')执行.
10 # 4) |                       管道...
11 # 5) ( ... )                 一个子shell
12 # 6) cd /dest/directory      改变当前目录到目标目录.
13 # 7) &&                      与操作,同上.
14 # 8) tar xpvf -              'x'解档,'p'保证所有权和文件属性,
15 #                            'v'发完整消息到stdout
16 #                            'f'后边跟'-',从stdin读取数据
17 #
18 #                            注意:'x' 是一个命令, 'p', 'v', 'f' 是选项.
19 # Whew!
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中有隐藏文件的话.
################################End Script#########################################

################################Start Script#######################################
1 bunzip2 linux-2.6.13.tar.bz2 | tar xvf -
2 # --未解压的tar文件--    | --然后把它传递到"tar"中--
3 # 如果 "tar" 没能够正常的处理"bunzip2",
4 # 这就需要使用管道来执行2个单独的步骤来完成它.
5 # 这个练习的目的是解档"bzipped"的kernel源文件.
################################End Script#########################################
        注意:在上边这个例子中'-'不太象是bash的操作符,而更像是tar的参数.
        bash$echo "whatever" | cat -
        whatever

        在需要一个文件名的地方,-重定向输出到stdout(如在tar和cf命令中),或者从
        stdin中接受输入,而不是从一个文件中接受输入.这是在管道中作为一个过滤
        器,来使用文件定位工具的一种办法.
        bash$file
        用法: file [-bciknvzl] [-f namefile] [-m magicfiles] file...
        上边这个例子file将会出错,提示你如何使用file命令.

        添加一个"-"将得到一个更有用的结果.这将使得shell等待用户输入.
        bash$file -
        abc
        standard input:                    ASCII text

        bash$file -
        #!/bin/bash
        standard input:                    Bourn-Again shell script tesxt executable

        现在命令从stdin中接受了输入,并分析它.

        "-"常用于管道后边的命令,具体参看33.7节,来看使用技巧.
        使用diff命令来和另一个文件的一部分进行比较.
        grep Linux file1 | diff file2 -

        最后,一个真实世界的使用tar命令的例子.

Example 3-4. 备份最后一天所有修改的文件.
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 #  在一个"tarball"中(经过tar和gzip处理过的文件)
 4 #+ 备份最后24小时当前目录下d所有修改的文件.
 5
 6 BACKUPFILE=backup-$(date +%m-%d-%Y)
 7 #                 在备份文件中嵌入时间.
 8 #                 Thanks, Joshua Tschida, for the idea.
 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 # Stephane Chazelas建议使用下边的两种代码之一
23 # -------------------------------------------------------------------
24 #   find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$archive.tar"
25 #      使用gnu版本的find.
26
27
28 #   find . -mtime -1 -type f -exec tar rvf "$archive.tar" '{}' \;
29 #         对于其他风格的UNIX便于移植,但是比较慢.
30 # -------------------------------------------------------------------
31
32
33 exit 0
################################End Script#########################################

        注意:以"-"开头的文件名在使用"-"作为重定向操作符的时候,可能会产生问题.
        应该写一个脚本来检查这个问题,并给这个文件加上合适的前缀.如:
        ./-FILENAME, $PWD/-FILENAME,或$PATHNAME/-FILENAME.

        如果变量的值以"-"开头,可能也会引起问题.
        1 var="-n"
        2 echo $var
        3 #具有"echo -n"的效果了,这样什么都不会输出的.

-        之前工作的目录."cd -"将回到之前的工作目录,具体请参考"$OLDPWD"环境变量.
        注意:一定要和之前讨论的重定向功能分开,但是只能依赖上下文区分.

-        算术减号.

=        算术等号,有时也用来比较字符串.
        1 a=28
        2 echo $a   # 28

+        算术加号,也用在正则表达式中.
+        选项,对于特定的命令来说使用"+"来打开特定的选项,用"-"来关闭特定的选项.

%        算术取模运算.也用在正则表达式中.

~        home目录.相当于$HOME变量.~bozo是bozo的home目录,并且ls ~bozo将列出其中的
        内容. ~/就是当前用户的home目录,并且ls ~/将列出其中的内容,如:
        bash$ echo ~bozo
        /home/bozo

        bash$ echo ~
        /home/bozo

        bash$ echo ~/
        /home/bozo/

        bash$ echo ~:
        /home/bozo:

        bash$ echo ~nonexistent-user
        ~nonexistent-user

~+        当前工作目录,相当于$PWD变量.

~-        之前的工作目录,相当于$OLDPWD内部变量.

=~        用于正则表达式,这个操作将在正则表达式匹配部分讲解,只有version3才支持.

^        行首,正则表达式中表示行首."^"定位到行首.


控制字符
        修改终端或文本显示的行为.控制字符以CONTROL + key组合.
        控制字符在脚本中不能正常使用.
        Ctl-B        光标后退,这应该依赖于bash输入的风格,默认是emacs风格的.
        Ctl-C        Break,终止前台工作.
        Ctl-D        从当前shell登出(和exit很像)
                    "EOF"(文件结束符).这也能从stdin中终止输入.
                    在console或者在xterm window中输入的时候,Ctl-D将删除光标下字符.
                    当没有字符时,Ctrl-D将退出当前会话.在xterm window也有关闭窗口
                    的效果.
        Ctl-G        beep.在一些老的终端,将响铃.
        Ctl-H        backspace,删除光标前边的字符.如:
                     1 #!/bin/bash
                     2 # 在一个变量中插入Ctl-H
                     3
                     4 a="^H^H"                  # 两个 Ctl-H (backspaces).
                     5 echo "abcdef"             # abcdef
                     6 echo -n "abcdef$a "       # abcd f
                     7 # 注意结尾的空格 ^              ^ 两个 twice.
                     8 echo -n "abcdef$a"        # abcdef
                     9 #  结尾没有空格             没有 backspace 的效果了(why?).
                    10                           # 结果并不像期望的那样
                    11 echo; echo
        Ctl-I        就是tab键.
        Ctl-J        新行.
        Ctl-K        垂直tab.(垂直tab?新颖,没听过)
                    作用就是删除光标到行尾的字符.
        Ctl-L        clear,清屏.
        Ctl-M        回车
################################Start Script#######################################
 1 #!/bin/bash
 2 # Thank you, Lee Maschmeyer, for this example.
 3
 4 read -n 1 -s -p $'Control-M leaves cursor at beginning of this line. Press Enter. \x0d'
 5                                   #当然,'0d'就是二进制的回车.
 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 #  In the [first vertical tab example] . . . the vertical tab
29 #  在这里[第一个垂直制表符的例子中] . . . 这个垂直制表符
30 #+ makes the printing go straight down without a carriage return.
31 #  This is true only on devices, such as the Linux console,
32 #+ that can't go "backward."
33 #  The real purpose of VT is to go straight UP, not down.
34 #  It can be used to print superscripts on a printer.
34 #  它可以用来在一个打印机上打印上标.
35 #  col的作用,可以用来模仿VT的合适的行为.
36
37 exit 0
################################End Script#########################################
        Ctl-Q        继续(等价于XON字符),这个继续的标准输入在一个终端里
        Ctl-S        挂起(等价于XOFF字符),这个被挂起的stdin在一个终端里,用Ctl-Q恢复
        Ctl-U        删除光标到行首的所有字符,在某些设置下,删除全行.
        Ctl-V        当输入字符时,Ctl-V允许插入控制字符.比如,下边2个例子是等价的
                    echo -e '\x0a'
                    echo <Ctl-V><Ctl-J>
                    Ctl-V在文本编辑器中十分有用,在vim中一样.
        Ctl-W        删除当前光标到前边的最近一个空格之间的字符.
                    在某些设置下,删除到第一个非字母或数字的字符.
        Ctl-Z        终止前台工作.
        
空白部分
        分割命令或者是变量.包括空格,tab,空行,或任何它们的组合.
        在一些特殊情况下,空白是不允许的,如变量赋值时,会引起语法错误.
        空白行在脚本中没有效果.
        "$IFS",对于某些命令输入的特殊变量分割域,默认使用的是空白.
        如果想保留空白,使用引用.

注意事项:
[1]        shell做大括号的命令扩展.但是命令本身需要对扩展的结果作处理.
[2]        例外:在pipe中的一个大括号中的代码段可能运行在一个子shell中.
        1 ls | { read firstline; read secondline; }
        2 #  错误,在打括号中的代码段,将运行到子shell中.
        3 #+ 所以ls的输出将不能传递到代码块中.
        4 echo "First line is $firstline; second line is $secondline"  # 不能工作
        5
        6 # Thanks, S.C.
[3]        换行符也被认为是空白.这也解释了为什么一个空行也会被认为是空白.



第4章 变量和参数的介绍
======================

4.1 变量替换
------------
$        变量替换操作符
        只有在变量被声明,赋值,unset或exported或者是在变量代表一个signal的时候,
        变量才会是以本来的面目出现在脚本里.变量在被赋值的时候,可能需要使用"=",
        read状态或者是在循环的头部.
        在""中还是会发生变量替换,这被叫做部分引用,或叫弱引用.而在''中就不会发生变
        量替换,这叫做全引用,也叫强引用.具体见第5章的讨论.

        注意:$var与${var}的区别,不加{},在某些上下文将引起错误,为了安全,使用2.
            具体见9.3节 参数替换.

Example 4-1. 变量赋值和替换
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 # 变量赋值和替换
 4
 5 a=375
 6 hello=$a
 7
 8 #-------------------------------------------------------------------------
 9 # 强烈注意,在赋值的前后一定不要有空格.
10 # 如果有空格会发生什么?
11
12 #  如果"VARIABLE =value",
13 #              ^
14 #+ 脚本将尝试运行一个"VARIABLE"的命令,带着一个"=value"参数.
15
16 #  如果"VARIABLE= value",
17 #               ^
18 #+ script tries to run "value" command with
18 #+ 脚本将尝试运行一个"value"的命令,带着
19 #+ the environmental variable "VARIABLE" set to "".
19 #+ 一个被赋成""值的环境变量"VARIABLE".
20 #-------------------------------------------------------------------------
21
22
23 echo hello    # 没有变量引用,不过是个hello字符串
24
25 echo $hello
26 echo ${hello} # 同上
27
28 echo "$hello"
29 echo "${hello}"
30
31 echo
32
33 hello="A B  C   D"
34 echo $hello   # A B C D
35 echo "$hello" # A B  C   D
36 # 就象你看到的echo $hello   和    echo "$hello"   将给出不同的结果.
37 #                                      ^      ^
38 # Quoting a variable preserves whitespace.
38 # 引用一个变量将保留其中的空白,当然,如果是变量替换就不会保留了.
39
40 echo
41
42 echo '$hello'  # $hello
43 #    ^      ^
44 # 全引用的作用
45 #+ 将导致"$"变成一个单独的字符.
46
47 # 注意两种引用不同的效果
48
49
50 hello=    # 设置为空值
51 echo "\$hello (null value) = $hello"
52 #  注意设置一个变量为空,与unset它,不是一回事,虽然看起来一样
53 #
54
55 # --------------------------------------------------------------
56
57 #  可以在同一行上设置多个变量.
58 #+ 要以空白分隔
59 #  小心,这会降低可读性,和可移植性.
60
61 var1=21  var2=22  var3=$V3
62 echo
63 echo "var1=$var1   var2=$var2   var3=$var3"
64
65 # 在老版本的"sh"上,可能会有问题.
66
67 # --------------------------------------------------------------
68
69 echo; echo
70
71 numbers="one two three"
72 #           ^   ^
73 other_numbers="1 2 3"
74 #               ^ ^
75 #  如果变量中有空白,那么引用就必要了.
76 #
77 echo "numbers = $numbers"
78 echo "other_numbers = $other_numbers"   # other_numbers = 1 2 3
79 echo
80
81 echo "uninitialized_variable = $uninitialized_variable"
82 # Uninitialized变量为空值(根本就没赋值).
83 uninitialized_variable=   #  声明,但是没被初始化
84                           #+ 其实和前边设置为空值得作用是一样的.
85 echo "uninitialized_variable = $uninitialized_variable"
86                           # 还是一个空值
87
88 uninitialized_variable=23       # 赋值
89 unset uninitialized_variable    # Unset it.
90 echo "uninitialized_variable = $uninitialized_variable"
91                                 # 还是空值
92 echo
93
94 exit 0
################################End Script#########################################
注意: 一个空值变量,或者是根本就没声明的变量,在赋值之前使用它可能会引起问题.
        但是还是可以用来做算术运算
################################Start Script#######################################
1 echo "$uninitialized"                                # (blank line)
2 let "uninitialized += 5"                             # Add 5 to it.
3 echo "$uninitialized"                                # 5
4
5 #  结论:
6 #  对于一个空值变量在做算术操作的时候,就好像它的值为0一样.
8 #  This is undocumented (and probably non-portable) behavior.
7 #  这并没被文档化(可能是不可移植)的行为.
################################End Script#########################################
具体参考    Example 11-21


4.2 变量赋值
------------
=        赋值操作符(前后都不能有空白)
        不要与-eq混淆,那个是test,并不是赋值.
        注意,=也可被用来做test操作,这依赖于上下文.

Example 4-2. 一般的变量赋值
################################Start Script#######################################
 1 #!/bin/bash
 2 # "裸体"变量
 3
 4 echo
 5
 6 # 变量什么时候是"裸体"的,比如前边少了$的时候.
 7 # 当它被赋值的时候,而不是被引用的时候.
 8
 9 # 赋值
10 a=879
11 echo "The value of \"a\" is $a."
12
13 # 使用let赋值
14 let a=16+5
15 echo "The value of \"a\" is now $a."
16
17 echo
18
19 # 在for循环中
20 echo -n "Values of \"a\" in the loop are: "
21 for a in 7 8 9 11
22 do
23   echo -n "$a "
24 done
25
26 echo
27 echo
28
29 # 在read命令状态中
30 echo -n "Enter \"a\" "
31 read a
32 echo "The value of \"a\" is now $a."
33
34 echo
35
36 exit 0
################################End Script#########################################

Example 4-3. 变量赋值,一般的和比较特殊的
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 a=23              # Simple case
 4 echo $a
 5 b=$a
 6 echo $b
 7
 8 # 现在让我们来点小变化
 9
10 a=`echo Hello!`   # 把echo命令的结果传给变量a
11 echo $a
12 #  注意,如果在命令扩展结构中使用一个(!)的话,在命令行中将不能工作
13 #+ 因为这触发了Bash的"历史机制".
14 #  但是,在校本里边使用的话,历史功能是被关闭的,所以就能够正常运行.
15
16
17 a=`ls -l`         # 把ls -l的结果给a
18 echo $a           # 别忘了,这么引用的话,ls的结果中的所有空白部分都没了(包括换行)
19 echo
20 echo "$a"         # 这么引用就正常了,保留了空白
21                   # (具体参阅章节"引用")
22
23 exit 0
################################End Script#########################################
使用$(...)机制进行的变量赋值(除去使用``来赋值的另外一种新方法).事实上这两种方法都是
命令替换的一种形式.
# 来自于/ect/rc.d/rc.local
R=$(cat /ect/redhat-release)
arch=$(uname -m)


4.3 Bash变量是不分类型的
------------------------
不像其他程序语言一样,Bash并不对变量区分"类型".本质上,Bash变量都是字符串.
但是依赖于上下文,Bash也允许比较操作和算术操作.决定这些的关键因素就是,变量中的值
是否只有数字.

Example 4-4 整型还是string?
################################Start Script#######################################
 1 #!/bin/bash
 2 # int-or-string.sh: 整形还是string?
 3
 4 a=2334                   # 整型
 5 let "a += 1"
 6 echo "a = $a "           # a = 2335
 7 echo                     # 还是整型
 8
 9
10 b=${a/23/BB}             # 将23替换成BB
11                          # 这将把b变量从整型变为string
12 echo "b = $b"            # b = BB35
13 declare -i b             # 即使使用declare命令也不会对此有任何帮助,9.4节有解释
14 echo "b = $b"            # b = BB35
15
16 let "b += 1"             # BB35 + 1 =
17 echo "b = $b"            # b = 1
18 echo
19
20 c=BB34
21 echo "c = $c"            # c = BB34
22 d=${c/BB/23}             # S将BB替换成23
23                          # 这使得$d变为一个整形
24 echo "d = $d"            # d = 2334
25 let "d += 1"             # 2334 + 1 =
26 echo "d = $d"            # d = 2335
27 echo
28
29 # 关于空变量怎么样?
30 e=""
31 echo "e = $e"            # e =
32 let "e += 1"             # 算术操作允许一个空变量?
33 echo "e = $e"            # e = 1
34 echo                     # 空变量将转换成一个整型变量
35
36 # 关于未声明的变量怎么样?
37 echo "f = $f"            # f =
38 let "f += 1"             # 算术操作允许么?
39 echo "f = $f"            # f = 1
40 echo                     # 未声明的变量将转换成一个整型变量
41
42
43
44 # 所以说Bash中的变量都是无类型的.
45
46 exit 0
################################End Script#########################################


4.4 特殊的变量类型
------------------
local variables
        这种变量只有在代码块或者是函数中才可见(具体见23.2和23章)
environmental variables
        这种变量将改变用户接口和shell的行为.

        在一般的上下文中,每个进程都有自己的环境,就是一组保持进程可能引用的信息的
        变量.这种情况下,shell于一个一般进程是相同的.

        每次当shell启动时,它都将创建自己的环境变量.更新或者添加新的环境变量,将导
        致shell更新它的环境,同时也会影响所有继承自这个环境的所有子进程(由这个命令
        导致的).

        注意:分配给环境变量的空间是受限的.创建太多的环境变量将引起空间溢出,这会引
        起问题.
        关于eval命令,具体见第11章
        bash$ eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`"
        bash$ du
        bash: /usr/bin/du: Argument list too long

        如果一个脚本设置了环境变量,需要export它,来通知本脚本的环境,这是export
        命令的功能,关于export命令,具体见11章.
        
        脚本只能对它产生的子进程export变量.一个从命令行被调用的脚本export的变量,将
        不能影响调用这个脚本的那个命令行shell的环境.

positional parameters
        就是从命令行中传进来的参数,$0, $1, $2, $3...

        $0就是脚本文件的名字,$1是第一个参数,$2为第2个...,参见[1](有$0的说明),$9
        以后就需要打括号了,如${10},${11},${12}...
        两个值得注意的变量$*和$@(第9章有具体的描述),表示所有的位置参数.

Example 4-5 位置参数
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 # 作为用例,调用这个脚本至少需要10个参数,如
 4 # ./scriptname 1 2 3 4 5 6 7 8 9 10
 5 MINPARAMS=10
 6
 7 echo
 8
 9 echo "The name of this script is \"$0\"."
10 # 添加./是为了当前目录
11 echo "The name of this script is \"`basename $0`\"."
12 # 去掉目录信息,具体见'basename'命令
13
14 echo
15
16 if [ -n "$1" ]              # 测试变量被被引用
17 then
18  echo "Parameter #1 is $1"  # "#"没被转义
19 fi
20
21 if [ -n "$2" ]
22 then
23  echo "Parameter #2 is $2"
24 fi
25
26 if [ -n "$3" ]
27 then
28  echo "Parameter #3 is $3"
29 fi
30
31 # ...
32
33
34 if [ -n "${10}" ]  # 大于9的参数必须出现在{}中.
35 then
36  echo "Parameter #10 is ${10}"
37 fi
38
39 echo "-----------------------------------"
40 echo "All the command-line parameters are: "$*""
41
42 if [ $# -lt "$MINPARAMS" ]        #$#是传到脚本里的位置参数的个数
43 then
44   echo
45   echo "This script needs at least $MINPARAMS command-line arguments!"
46 fi  
47
48 echo
49
50 exit 0
################################End Script#########################################
        {}标记法是一种很好的使用位置参数的方法.这也需要间接引用(见Example 34-2)
        1 args=$#           # 位置参数的个数
        2 lastarg=${!args}
        3 # 或:       lastarg=${!#}
        4 # 注意 lastarg=${!$#} 将报错

        一些脚本可能会依赖于使用不同的调用名字,而表现出不同的行为,这样一般都需要
        判断$0,而其他的名字都是通过ln命令产生的链接.(具体参见Example 12-2)

        如果脚本需要一个命令行参数,而调用的时候,没用这个参数,这就有可能造成分配一个
        空变量,这样估计就会引起问题.一种解决办法就是在这个位置参数,和相关的变量后
        边,都添加一个额外的字符.具体见下边的例子.
################################Start Script#######################################
 1 variable1_=$1_  # 而不是 variable1=$1
 2 # 这将阻止一个错误,即使在调用时没使用这个位置参数.
 3
 4 critical_argument01=$variable1_
 5
 6 # 这个扩展的字符是可以被消除掉的,就像这样.
 7 variable1=${variable1_/_/}
 8 # 副作用就是$variable1_多了一个下划线
 9 # 这里使用了一个参数替换模版(后边会有具体的讨论)
10 # (Leaving out the replacement pattern results in a deletion.)
10 # (在一个删除动作中,节省了一个替换模式)
11
12
13 # 一个解决这种问题的更简单的做法就是,判断一下这个位置参数是否传递下来了
14 if [ -z $1 ]
15 then
16   exit $E_MISSING_POS_PARAM
17 fi
18
19
20 #  但是上边的方法将可能产生一个意外的副作用
21 #  参数替换的更好的办法应该是:
22 #         ${1:-$DefaultVal}
23 #  具体察看"Parameter Substition"节
24 #+ 在第9章
################################End Script#########################################
        

Example 4-6 wh,whois节点名字查询
################################Start Script#######################################
 1 #!/bin/bash
 2 # ex18.sh
 3
 4 # Does a 'whois domain-name' lookup on any of 3 alternate servers:
 5 #                    ripe.net, cw.net, radb.net
 6
 7 # 把这个脚本重命名为'wh',然后放到/usr/local/bin下
 8
 9 # 需要3个符号链接
10 # ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
11 # ln -s /usr/local/bin/wh /usr/local/bin/wh-cw
12 # ln -s /usr/local/bin/wh /usr/local/bin/wh-radb
13
14 E_NOARGS=65
15
16
17 if [ -z "$1" ]
18 then
19   echo "Usage: `basename $0` [domain-name]"
20   exit $E_NOARGS
21 fi
22
23 # Check script name and call proper server.
23 # 检查脚本名字,然后调用合适的服务器
24 case `basename $0` in    # Or:    case ${0##*/} in
25     "wh"     ) whois $1@whois.ripe.net;;
26     "wh-ripe") whois $1@whois.ripe.net;;
27     "wh-radb") whois $1@whois.radb.net;;
28     "wh-cw"  ) whois $1@whois.cw.net;;
29     *        ) echo "Usage: `basename $0` [domain-name]";;
30 esac
31
32 exit $?
################################End Script#########################################

shift        shift命令重新分配位置参数,其实就是向左移动一个位置.
    $1 <--- $2, $2 <--- $3, $3 <--- $4, 等等.
        老的$1将消失,但是$0(脚本名)是不会改变的.如果你使用了大量的位置参数,那么
        shift命令允许你存取超过10个参数.虽然{}表示法也允许这样.


Example 4-7 使用shift
################################Start Script#######################################
 1 #!/bin/bash
 2 # 使用'shift'来穿过所有的位置参数.
 3
 4 #  把这个脚本命名为shft,
 5 #+ 并且使用一些参数来调用它,如:
 6 #          ./shft a b c def 23 skidoo
 7
 8 until [ -z "$1" ]  # 知道所有参数都用光
 9 do
10   echo -n "$1 "
11   shift
12 done
13
14 echo               # 额外的换行.
15
16 exit 0
################################End Script#########################################
        在将参数传递到函数中时,shift的工作方式也基本差不多.具体见Example 33-15

注意事项:
[1]        进程调用设置$0参数的脚本.一般的,这个参数就是脚本名字.具体察看execv的man页.



第5章 引用(翻译的可能有问题,特指引号)
======================================
引号的特殊效果就是,保护字符串中的特殊字符不被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中,当我们引用一个字符串,
我们是保护它的字面含义.

特定的程序和工具能够重新解释或扩展特殊的字符.引用的一个重要的作用就是保护命令行中
的参数,但还是允许正在调用的程序来扩展它.
 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下的行为(其实就是正则表达式么),[1]

引用还可以抑制echo命令的换行作用.

bash$ echo $(ls -l)
total 8 -rw-rw-r-- 1 bozo bozo 130 Aug 21 12:57 t222.sh -rw-rw-r-- 1 bozo bozo 78 Aug 21 12:57 t71.sh

bash$ echo "$(ls -l)"
total 8
-rw-rw-r--  1 bozo bozo 130 Aug 21 12:57 t222.sh
-rw-rw-r--  1 bozo bozo  78 Aug 21 12:57 t71.sh


5.1 引用变量
------------
在一个双引号中直接使用变量名,一般都是没有问题的.它阻止了所有在引号中的特殊字符的
重新解释--包括变量名[2]--但是$,`和\除外.[3]保留$,作为特殊字符的意义,是为了能够在双
引号中也能够正常地引用变量("$var").这样在""中可以使用变量所表达的值(Example 4-1).

使用""来防止单词分割.[4]如果在参数列表中使用双引号,将使得双引号中的参数作为一个参
数.即使双引号中的字符串包含多个单词(也就是包含空白部分),也不会变为多个参数,如:
 1 variable1="a variable containing five words"
 2 COMMAND This is $variable1    # COMMAND将以7个参数来执行
 3 # "This" "is" "a" "variable" "containing" "five" "words"
 4
 5 COMMAND "This is $variable1"  # COMMAND将以1个参数来执行
 6 # "This is a variable containing five words"
 7
 8
 9 variable2=""    # 空值
10
11 COMMAND $variable2 $variable2 $variable2        # COMMAND将不带参数执行
12 COMMAND "$variable2" "$variable2" "$variable2"  # COMMAND将以3个空参数来执行
13 COMMAND "$variable2 $variable2 $variable2"      # COMMAND将以1个参数来执行(2空格)
    用双引号把参数封到echo中是很有必要的,只有在单词分隔或时保留空白时的时候可能
    有些问题.

Example 5-1 echo一些诡异的变量
################################Start Script#######################################
 1 #!/bin/bash
 2 # weirdvars.sh: echo诡异的变量
 3
 4 var="'(]\\{}\$\""
 5 echo $var        # '(]\{}$"
 6 echo "$var"      # '(]\{}$"    并没有什么不同
 7
 8 echo
 9
10 IFS='\'
11 echo $var        # '(] {}$"     \ 转换成空格了?明显和IFS有关系么!又不傻!
12 echo "$var"      # '(]\{}$"
13
14 exit 0
################################End Script#########################################

单引号操作总体上和""很像,但不允许引用变量.因为$的特殊含义被关闭了.在''中除了',其他
字符都没有特殊的含义了.所以单引号比双引号严格.
    因为即使是\,在''中都被关闭了,所以你想在''中显示'的含义,将得不到预期的效果.
   1 echo "Why can't I write 's between single quotes"
   2
   3 echo
   4
   5 # 一种绕弯的方法
   6 echo 'Why can'\''t I write '"'"'s between single quotes'
   7 #    |-------|  |----------|   |-----------------------|
   8 # 包含了2个单引号字符,原书好像有错误

注意事项:
[1]        除非当前目录下,正好有个叫first的文件.
[2]        即使是变量的值也是有副作用的(见下边)
[3]        如果在""中包含"!"的话,在命令行中将会出现错误.因为这个"!"被当作历史命令来解释了.
        在一个脚本中,这种情况是不会发生的,因为在脚本中,Bash历史记录被关闭了.

        下边是一些关于"\"一些不协调的行为.
        bash$ echo hello\!
        hello!

        bash$ echo "hello\!"
        hello\!

        bash$ echo -e x\ty
        xty

        bash$ echo -e "x\ty"
        x       y

[4]        "单词分隔",在这个上下文中意味着,将一个字符串分隔为一些分离的参数.


5.2 转义(\)
-----------
转义是一种引用单个字符的方法.一个具有特殊含义的字符前边放上一个转义符(\)就告诉shell
这个字符失去了特殊的含义.
    值得注意的是,在某些特定的命令和工具中,比如echo和sed,转义符往往会起到相反的效果,
    它反倒有可能引发出这个字符特殊的含义.

对于特定的转义符的特殊的含义
在echo和sed中所使用的
\n        意味着新的一行
\r        回车
\t        tab键
\v        vertical tab(垂直tab),查前边的Ctl-K
\b        backspace,查前边的Ctl-H
\a        "alert"(如beep或flash)
\0xx    转换成8进制ASCII解码,等价于oxx

Example 5-2 转义符
################################Start Script#######################################
 1 #!/bin/bash
 2 # escaped.sh: 转义符
 3
 4 echo; echo
 5
 6 echo "\v\v\v\v"      # 逐字的打印\v\v\v\v .
 7 # 使用-e选项的echo命令来打印转义符
 8 echo "============="
 9 echo "VERTICAL TABS"
10 echo -e "\v\v\v\v"   # Prints 4 vertical tabs.
11 echo "=============="
12
13 echo "QUOTATION MARK"
14 echo -e "\042"       # 打印" (引号, 8进制的ASCII 码就是42).
15 echo "=============="
16
17 # The $'\X' construct makes the -e option unnecessary.
17 # 如果使用$'\X'结构,那-e选项就不必要了
18 echo; echo "NEWLINE AND BEEP"
19 echo $'\n'           # 新行.
20 echo $'\a'           # Alert (beep).
21
22 echo "==============="
23 echo "QUOTATION MARKS"
24 # 版本2以后Bash允许使用$'\nnn'结构
25 # 注意这种情况,'\nnn\是8进制
26 echo $'\t \042 \t'   # Quote (") framed by tabs.
27
28 # 当然,也可以使用16进制的值,使用$'\xhhh' 结构
29 echo $'\t \x22 \t'  # Quote (") framed by tabs.
30
31 # 早一点的Bash版本允许'\x022'这种形式
32 echo "==============="
33 echo
34
35
36 # 分配ASCII字符到变量中
37 # ---------------------
38 quote=$'\042'        # \042是",分配到变量中
39 echo "$quote This is a quoted string, $quote and this lies outside the quotes."
40
41 echo
42
43 # Concatenating ASCII chars in a variable.
43 # 变量中的连续的ASCII char.
44 triple_underline=$'\137\137\137'  # 137 是8进制的ASCII 码'_'.
45 echo "$triple_underline UNDERLINE $triple_underline"
46
47 echo
48
49 ABC=$'\101\102\103\010'           # 101, 102, 103 是8进制的码A, B, C.
50 echo $ABC
51
52 echo; echo
53
54 escape=$'\033'                    # 033 是8进制码for escape.
55 echo "\"escape\" echoes as $escape"
56 #"escape" echoes as                  没有变量被输出
57
58 echo; echo
59
60 exit 0
################################End Script#########################################
    另一个关于$''字符串扩展结果的例子见Example 34-1

\"        表达引号本身
        1 echo "Hello"                  # Hello
        2 echo "\"Hello\", he said."    # "Hello", he said.

\$        $号本身,跟在\$后的变量名,将不能扩展
        1 echo "\$variable01"  # 结果是$variable01

\\        \号本身.
        1 echo "\\"  # 结果是\
        2
        3 # 相反的 . . .
        4
        5 echo "\"   # 这会出现第2个命令提示符,说白了就是提示你命令不全,你再补个"就
        6             # 好了.如果是在脚本里,就会给出一个错误.

    注意:\的行为依赖于它是否被转义,被"",或者是否在"命令替换"和"here document"中.
################################Start Script#######################################
 1                       #  简单的转义和""
 2 echo \z               #  z
 3 echo \\z              # \z
 4 echo '\z'             # \z
 5 echo '\\z'            # \\z
 6 echo "\z"             # \z
 7 echo "\\z"            # \z
 8
 9                       #  命令替换
10 echo `echo \z`        #  z
11 echo `echo \\z`       #  z
12 echo `echo \\\z`      # \z
13 echo `echo \\\\z`     # \z
14 echo `echo \\\\\\z`   # \z
15 echo `echo \\\\\\\z`  # \\z
16 echo `echo "\z"`      # \z
17 echo `echo "\\z"`     # \z
18
19                       # Here document
20 cat <<EOF              
21 \z                      
22 EOF                   # \z
23
24 cat <<EOF              
25 \\z                     
26 EOF                   # \z
################################End Script#########################################

    分配给变量的字符串的元素也会被转义,但是只把一个转义符分配给变量将会报错.
################################Start Script#######################################
 1 variable=\
 2 echo "$variable"
 3 # Will not work - gives an error message:
 3 # 将不能正常工作- 将给出一个错误消息:
 4 # test.sh: : command not found
 5 # 一个"裸体的" 转义符将不能够安全的分配给变量.
 6 #
 7 #  What actually happens here is that the "\" escapes the newline and
 7 #  这里其实真正发生的是variable=\,这句被shell认为是没有完成,\被认为是一个续行符
 8 #+ 这样,下边的这句echo,也被认为是上一行的补充.所以,总的来说就是一个非法变量分配
 9
10 variable=\
11 23skidoo
12 echo "$variable"        #  23skidoo
13                         #  这句就可以使用,因为这是一个合法的变量分配
14
15 variable=\
16 #        \^   转义一个空格
17 echo "$variable"        # 显示空格
18
19 variable=\\
20 echo "$variable"        # \
21
22 variable=\\\
23 echo "$variable"
24 # 不能正常工作,给出一个错误
25 # test.sh: \: command not found
26 #
27 #  第一个转义符把第2个\转义了,但是第3个又变成"裸体的"了,
28 #+ 与上边的例子的原因相同
29
30 variable=\\\\
31 echo "$variable"        # \\
32                         # 转了两个\
33                         # 没问题
################################End Script#########################################

转义一个空格,在命令行参数列表中将会阻止单词分隔问题.
################################Start Script#######################################
 1 file_list="/bin/cat /bin/gzip /bin/more /usr/bin/less /usr/bin/emacs-20.7"
 2 # 列出的文件都作为命令的参数.
 3
 4 # Add two files to the list, and list all.
 4 # 加2个文件到list中,并且列出全部.
 5 ls -l /usr/X11R6/bin/xsetroot /sbin/dump $file_list
 6
 7 echo "-------------------------------------------------------------------------"
 8
 9 # 如果我们转义2个空格,会发生什么?
10 ls -l /usr/X11R6/bin/xsetroot\ /sbin/dump\ $file_list
11 # 错误: 因为前3个路径名被合并成一个参数传给了'ls -l'
12 #        因为2个转义符阻止了参数(单词)分离
################################End Script#########################################

转义符也提供续行功能.一般,每一行都包含一个不同的命令,但如果在行尾加上\,那就会接受
新行的输入,作为这一行的补充.
1 (cd /source/directory && tar cf - . ) | \
2 (cd /dest/directory && tar xpvf -)
3 # 重复了 Alan Cox的目录树拷贝命令
4 # 为了增加可读性分成2行.
5
6 # 也可以使用如下方式:
7 tar cf - -C /source/directory . |
8 tar xpvf - -C /dest/directory
9 # 察看下边的注意事项

注意:如果一个脚本以|(管道字符)结束.那么一个\(转义符),就不用非加上不可了.
    但是一个好的shell脚本编写风格,还是应该在行尾加上\,以增加可读性.
################################Start Script#######################################
 1 echo "foo
 2 bar"
 3 #foo
 4 #bar
 5
 6 echo
 7
 8 echo 'foo
 9 bar'    # 没区别
10 #foo
11 #bar
12
13 echo
14
15 echo foo\
16 bar     # 续行
17 #foobar
18
19 echo
20
21 echo "foo\
22 bar"     # 与上边一样,\还是作为续行符
23 #foobar
24
25 echo
26
27 echo 'foo\
28 bar'     # 由于是强引用,所以\没被解释成续行符
29 #foo\
30 #bar
################################End Script#########################################

返回顶部

发表评论:

Powered By Z-BlogPHP 1.7.3


知识共享许可协议
本作品采用知识共享署名 3.0 中国大陆许可协议进行许可。
网站备案号粤ICP备15104741号-1