Top

D4:学习Shell之四

像Shell一样看世界

本章只有一个新命令:echo——显示文本的行。

扩展(expansion)

每次我们键入命令并按Enter键时,bash都会在执行命令之前对文本进行几次替换。比如,通配符*对shell有很多意义。
实现这一点的过程称为扩展。随着扩展,在shell作用于它之前,它被扩展成另一个东西。

举例:
[me@linuxbox ~]$ echo this is a test
this is a test
很简单,echo命令将它后面所有字符显示出来。下一个例子:
[me@linuxbox ~]$ echo *
Desktop Documents ls-output.txt Music Pictures Public Templates Videos	
由于使用了通配符*,在执行echo命令之前,shell会将*扩展为其他内容(本例中是当前工作目录中的文件名)。当按下回车键时,shell会在执行命令之前自动展开命令行上的所有限定字符。因此echo命令不会看到*,只看到展开后的结果。
同理,如果使用
echo ???
则会显示当前目录下所有文件名中只有三个字母的文件的名称。

路径名扩展

通配符工作的机制称为路径名扩展。假定当前目录下有以下内容:
[me@linuxbox ~]$ ls
Desktop ls-output.txt Pictures Templates
Documents Music Public Videos	
执行以下命令的结果:
[me@linuxbox ~]$ echo D*
Desktop  Documents
而执行以下命令的结果:
[me@linuxbox ~]$ echo *s
Documents Pictures Templates Videos	
或者可以用以下方式(tcsh失败,sh成功):
[me@linuxbox ~]$ echo [[:upper:]]*
Desktop Documents Music Pictures Public Templates Videos	
在主目录之外可以这样做:
[me@linuxbox ~]$ echo /usr/*/share
/usr/kerberos/share /usr/local/share	

隐藏文件的路径名扩展

以句点开头的文件名是隐藏的,所以路径名扩展也遵从这种行为。
echo .*可以显示当前目录下所有隐藏文件,但也会显示.和..这两个名称。当我们想在脚本中对当前目录文件进行轮询操作时,这种结果可能会带来一些问题。
比如:
ls -d .* | less
为了在这种情况下更好地执行路径名扩展,必须采取更具体的模式:
echo .[!.]*
此模式扩展到每个文件名,该文件名仅以一个句点开头,后跟任何其他字符。这将适用于大多数隐藏文件(尽管它仍然不包括具有多个前导句点的文件名)。

带有-A选项(“几乎全部”)的ls命令将提供隐藏文件的正确列表:
ls -A

波浪线(tilde,~)扩展

当一个单词以波浪线开头使用时,它会扩展为指定用户的主目录的名称。如果没有指定用户,则扩展为当前用户的主目录名称。例如:
[me@linuxbox ~]$ echo ~
/home/me
以下命令显示用户“foo”的主目录名称:
[me@linuxbox ~]$ echo ~foo
/home/foo

算术扩展

算术扩展这用以下样式:
echo $((expression))
tcsh的格式为(sh也支持这种格式):
echo "expression" | bc
其中expression是由指和算术运算符组成的算术表达式。
算术扩展只支持整数,可以执行的运算有:
运算符 说明
+ 加(addition)
- 减(subtraction)
* 乘(Multiplication)
/ 除(division),由于扩展仅支持整数,所以结果得出的结果也是整数
% 取模(modulo),也就是余数
** 指数(exponentiation)
算术表达式中空格不重要,表达式可以嵌套。例如:
[me@linuxbox ~]$ echo $(($((5**2)) * 3))
75
单括号可用于分组多个表达式。使用这种技术,可以重写上面的示例,并使用单个扩展获得相同的结果:
[me@linuxbox ~]$ echo $(((5**2) * 3))
75
下面是一个使用除法和余数运算符的示例。注意整数除法的效果。
[me@linuxbox ~]$ echo Five divided by two equals $((5/2))
Five divided by two equals 2
[me@linuxbox ~]$ echo with $((5%2)) left over.
with 1 left over.	

大括号扩展(brace expansion)

可以从包含大括号的模式中创建多个文本字符串,例如:
[me@linixbox ~]$ echo Front-{A,B,C}-Back
Front-A-Back Front-B-Back Front-C-Back

bash可以使用范围值,例如:
echo Number_{1..5}
并且支持零填充:
[me@linuxbox ~]$ echo {01..15}
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
[me@linuxbox ~]$ echo {001..15}
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015	
也可以用字母范围:
[me@linuxbox ~]$ echo {Z..A}
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A	
嵌套:
[me@linuxbox ~]$ echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b	

参数扩展

[me@linuxbox ~]$ echo $USER
me	
它在shell脚本中比直接在命令行中更有用。它的许多功能都与系统存储小块数据并为每个数据块命名的能力有关。许多这样的块,更恰当地称为变量,可供我们检查。
以下命令可以列出可用的参数:
[me@linuxbox~ ]$ printenv | less
如果输入错误模式,则echo将不会显示任何东西。

命令替换

命令替换允许将命令的输出用作扩展:
[me@linuxbox ~]$ echo $(ls)
Desktop Documents ls-output.txt Music Pictures Public Templates Videos	
以下命令将cp的结果作为参数传递给ls命令,从而获取cp程序的列表,而无需直到其完整路径名:
[me@linuxbox ~]$ ls -l $(which cp)
-rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp	
不仅限于简单的命令,还可以使用整个管道:
[me@linuxbox ~]$ file $(ls -d /usr/bin/* | grep zip)
/usr/bin/bunzip2:      symbolic link to `bzip2'
/usr/bin/bzip2:        ELF 32-bit LSB executable, Intel 80386,
version 1 (SYSV), dynamically linked (uses shared libs), for
GNU/Linux 2.6.9, stripped
/usr/bin/bzip2recover: ELF 32-bit LSB executable, Intel 80386,
version 1 (SYSV), dynamically linked (uses shared libs), for
GNU/Linux 2.6.9, stripped
/usr/bin/funzip:       ELF 32-bit LSB executable, Intel 80386,
version 1 (SYSV), dynamically linked (uses shared libs), for
GNU/Linux 2.6.9, stripped
/usr/bin/gpg-zip:      Bourne shell script text executable
/usr/bin/gunzip:       symbolic link to `../../bin/gunzip'
/usr/bin/gzip:         symbolic link to `../../bin/gzip'
/usr/bin/mzip:         symbolic link to `mtools'	
...	
上面例子中,管道的结果称为file命令的参数列表。
在旧的shell程序中(比如tcsh),有一种替代的命令替换语法,bash也支持这种语法。它使用反引号(`,backquotes),而不是$符号和括号。
[me@linuxbox ~]$ ls -l `which cp`
-rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp	

引号(quoting)

下面两个例子:
[me@linuxbox ~]$ echo this is a test
this is a test	
在上面一个示例中,shell的分词从echo命令的参数列表中删除了额外的空白。
在下面示例中,参数展开用空字符串替换$1的值,因为它是一个未定义的变量。shell提供了一种称为quoting的机制来选择性地抑制不必要的扩展。
[me@linuxbox ~]$ echo The total is $100.00
The total is 00.00	

双引号

第一种引用是双引号。如果将文本放在双引号内,shell使用的所有特殊字符将失去其特殊意义,并被视为普通字符。例外情况是$、\(反斜杠)、`(反引号)。
这意味着将抑制分词、路径名扩展、波浪线扩展和大括号扩展,但仍将执行参数扩展、算术扩展和命令替换。
使用双引号可以处理包含嵌入空格的文件名。例如:
[me@linuxbox ~]$ ls -l two words.txt
ls: cannot access two: No such file or directory
ls: cannot access words.txt: No such file or directory	
如果使用双引号,效果如下:
[me@linuxbox ~]$ ls -l "two words.txt"
-rw-rw-r-- 1 me me 18 2016-02-20 13:03 two words.txt
[me@linuxbox ~]$ mv "two words.txt" two_words.txt	
默认情况下,分词查找是否存在空格、制表符和换行符,并将它们视为单词之间的分隔符。这意味着不带引号的空格、制表符和换行符将不被视为文本的一部分,只起到分隔作用。
[me@linuxbox ~]$ echo $(cal)
February 2019 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
[me@linuxbox ~]$ echo "$(cal)"
February 2019
Su Mo Tu We Th Fr Sa
                1  2 
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29
在第一个实例中,无引号的命令替换产生了一个包含38个参数的命令行。在第二个例子中,它生成了一个包含嵌入空格和换行符的参数的命令行。

单引号

单引号可以抑制所有扩展。以下是无引号、双引号和单引号的比较:
[me@linuxbox ~]$ echo text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
text /home/me/ls-output.txt a b foo 4 me
[me@linuxbox ~]$ echo "text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER"
text ~/*.txt {a,b} foo 4 me
[me@linuxbox ~]$ echo 'text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER'
text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER	
	

转义(escaping)字符

在一个字符前面加上一个反斜杠,可以应用这个字符。通常在双引号内使用,可选择性的放置扩展。
[me@linuxbox ~]$ echo "The balance for user $USER is: \$5.00"
The balance for user me is: $5.00	
使用转义消除文件名中字符的特殊含义是常见的应用场景。例如在文件名中如果有一些对于shell具有特殊含义的字符,比如$、!、&和空格之类的,可以执行以下操作:
[me@linuxbox ~]$ mv bad\&filename good_filename
\\可以转义一个反斜杠,但要注意,在单引号中,反斜杠失去其特殊含义,被视为普通字符。

反斜杠转义序列

除了用作转义字符外,反斜杠还用作表示某些特殊字符(称为控制代码)的符号的一部分。
ASCII编码方案中的前32个字符用于向类似电传打字机的设备发送命令。其中一些代码是熟悉的(制表符、退格、换行符、回车符),而其他代码则很生僻(null、传输结束和确认)
转义序列 含义
\a 铃(导致计算机发出哔哔声的警报)
\b 回退
\n 换行。在类Unix系统上,这会生成一个换行符
\r 回车
\t 制表符
上表列出了一些常见的反斜杠转义序列。这种使用反斜杠表示法背后的思想起源于C编程语言,并已被包括shell在内的许多其他语言采用。
-e选项添加到echo命令中将启用转义序列的解释。
以下两个命令效果是相同的:
sleep 10; echo -e "Time's up\a"
sleep 10; echo "Time's up" $'a\'