首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

8.9  处理命令行

不论是采用交互方式还是运行shell脚本,bash在处理命令行之前都需要读取该命令行,即bash在处理一条命令之前总是读取至少一行命令。有些bash内置命令可能跨越多行,比如if和else,还有函数和引用字符串。如果bash得知某条命令跨越多行,那么在处理该命令之前需要读取整条命令。在交互式会话中,当用户输入多行命令中的每一行时,bash都使用次提示符(PS2,默认为>)提示用户,直到bash识别出命令的末尾:

$ echo 'hi

> end'

hi

end

$ function hello () {

> echo hello there

> }

$

在读取一个命令行之后,bash对该行应用历史扩展和别名替换。

8.9.1  历史扩展

第8.5.2节讨论了一些命令,使用它们可以修改和重新执行命令历史列表中的命令行。历史扩展是bash将历史命令转换到可执行命令行的过程。举例来说,当给出命令!!时,历史扩展将改变命令行,以使其内容与前一个命令行一样。默认情况下,对于交互式shell,历史扩展特性是开启的,使用set +o histexpand可将其关闭。历史扩展不能用于非交互式shell(shell脚本)。

8.9.2  别名替换

别名将一个简单命令的第1个字替换为一个字符串。默认情况下,交互式shell的别名特性是开启的,在非交互式shell中则是关闭的。使用命令shopt -u expand_aliases可将别名关闭。

8.9.3  解析和扫描命令行

在处理完历史命令和别名之后,bash并不立即执行命令。首先是将命令行解析成标记或者字(分割成多个字符串),然后shell扫描每个标记,查找特殊字符和模式,shell需要对它们采取某些动作。这些动作可能涉及替换一个字或者将某个字替换成另一个字。当shell解析下面的命令行时,将其分解成3个标记(cp、~/letter和.):

$ cp ~/letter .

在把命令行分隔成若干标记之后,开始执行该命令之前,shell将扫描这些标记并进行命令行扩展。

8.9.4  命令行扩展

在交互式用法和非交互式用法中,shell在将命令行传递给被调用程序之前均使用命令行扩展。在不太了解命令行扩展的情况下也可以使用shell,但是当理解这部分主题之后,就可更充分的利用shell优势。本节内容覆盖了Bourne Again Shell命令行扩展,TC Shell命令行扩展相关内容将从第9.3.1节开始。

Bourne Again Shell按照下面的顺序依次扫描每个标记,进行不同类型的扩展和替换。大部分处理是将一个字扩展成一个单个的字。只有花括号扩展、字划分和路径名扩展可能改变命令中字的数目(除了变量“$@”的扩展之外)。

① 花括号扩展

② 代字符扩展

③ 参数扩展和变量扩展

④ 算术扩展

⑤ 命令替换

⑥ 分词

⑦ 路径名扩展

⑧ 处理替换

引用删除  在bash处理完上面的列表之后,它将那些不是扩展结果的单引号、双引号和反斜杠从命令行中删除。这个过程称为引用删除(quote removal)。

1. 扩展顺序

bash执行这些步骤的顺序会影响到命令的解释。举例来说,如果将变量设置为一个看上去像一个输出重定向的指令,然后输入一条使用该变量的值执行重定向,用户将期待bash重定向输出。

$ SENDIT="> /tmp/saveit"

$ echo xxx $SENDIT

xxx > /tmp/saveit

$ cat /tmp/saveit

cat: /tmp/saveit: No such file or directory

实际上,shell并不会重定向输出,它在计算变量的值之前识别输入重定向和输出重定向。当shell执行该命令行时,检查重定向,但发现没有时,计算SENDIT变量。在用> /tmp/saveit替换到该变量之后,bash把这些参数传递给echo,echo将负责将它的参数复制到标准输出。在这个过程中并不创建/tmp/saveit文件。

下面的几节更加详尽地描述了命令处理涉及的几个步骤。切记双引号和单引号将使shell在进行扩展时表现出不同的行为。双引号允许参数和变量扩展,但是抑制其他类型的扩展,而单引号则抑制所有的扩展。

2. 花括号扩展

花括号扩展源自C Shell,当不能应用路径名扩展时,它为指定文件名提供了一个便利的方式。尽管花括号扩展主要用于指定文件名,该机制还可以用来产生任意字符串。shell不会试着去用已有文件的名称去匹配花括号。

花括号扩展在交互式shell和非交互式shell中都是默认开启的,可以使用命令set +o braceexpand关闭它。shell还使用花括号来分隔变量名。

下面的示例演示了花括号扩展的工作原理。因为工作目录下没有任何文件,所以ls命令不会显示任何输出。内置命令echo显示了shell使用花括号扩展产生的字符串。此时,该字符串并不匹配文件名(在工作目录下没有文件)。

$ ls

$ echo chap_{one,two,three}.txt

chap_one.txt chap_two.txt chap_three.txt

shell将echo命令的花括号中以分号分隔开的字符串扩展成一个以空格符分隔开的字符串列表。该列表中的每一个字符串都被加上了字符串chap_,这称之为前缀;同时还被附加了字符串.txt,而这称之为后缀。无论是前缀还是后缀都是可选的。花括号中字符串的从左至右的顺序在扩展过程中仍然会保持。为了让shell特殊对待左右花括号并进行花括号扩展,花括号里面至少要有一个逗号并且没有未引用的空白字符。花括号扩展可以嵌套。

当有较长的前缀或者后缀时,花括号扩展很有用。下面的示例将位于目录/usr/local/src/C下的4个文件main.c、f1.c、f2.c和tmp.c复制到工作目录下:

$ cp /usr/1oca1/src/C/{main,fl,f2,tmp}.c .

还可以使用花括号扩展用相关的名字创建目录:

$ ls -F

filel fi1e2 fi1e3

$ mkdir vrs{A,B,C,D,E}

$ ls -F

filel fi1e2 fi1e3 vrsA/ vrsB/ vrsC/ vrsD/ vrsE/

-F选项使ls在目录后面显示斜杠(/),在可执行文件后面显示星号(*)。

如果试着用一个模糊文件引用代替花括号来指定目录,结果将有所不同(并不是所想要的结果):

$ rmdir vrs*

$ mkdir vrs[A-E]

$ ls -F

filel f11e2 f11e3 vrs[A-E]/

模糊文件引用匹配已存在文件的名称。因为bash发现没有文件名匹配vrs[A-E],它将这个模糊文件引用传递给mkdir,后者用这个名称创建一个目录。第5.4.3节中有一个关于模糊文件引用中括号的讨论。

3. 代字符扩展

第4章给出了一个用于指定用户主目录或者其他用户主目录的快捷助记符。本节将更加详细地解释代字符扩展。

当代字符(~)出现在命令行中某字的起始处时,它就属于一个特殊的字符。当bash在这个位置上看到代字符时,它将后面的字符串(在第1个斜杠之前或者如果没有斜杠,到达字的末尾)作为一个可能的登录名。如果这个可能的登录名是空的(也就是说带字符本身作为一个字,或者其后紧跟着一个斜杠),那么shell将用HOME变量的值取代这个代字符。下面的示例演示了这个扩展,其中最后一条命令将名为letter的文件从alex的主目录复制到工作目录:

$ echo $HOME

/home/alex

$ echo ~

/home/alex

$ echo ~/letter

/home/alex/lette r

$ cp ~/letter .

如果代字符后面的字符串构成了一个合法的登录名,那么shell将用与该登录名相对应的主目录路径名来替换这个代字符和名字。如果它不为空并且不是一个合法的登录名,那么shell将不会进行任何替换:

$ echo ~jenny

/home/jenny

$ echo ~root

/root

$ echo ~xx

~xx

代字符还可用于目录栈操作中。另外,~+是PWD(工作目录名)的同义词,~-是OLDPWD(前一个工作目录名)的同义词。

4. 参数扩展和变量扩展

在命令行中,后面没有开放的圆括号的美元符号($)将引入参数或者变量扩展。参数包括命令行,或者位置参数和特殊参数。变量包括用户创建变量和关键字变量。但是,bash的man手册页和info页面没有区分这些。

如果参数和变量用单引号引起来或者开头的美元符号被转义(前面加上了反斜杠),那么这些参数和变量将不会扩展。如果用双引号将它们引起来,那么shell将扩展参数和变量。

5. 算术扩展

shell计算算术表达式的值并用该值替换表达式,这就是算术扩展。关于tcsh下的算术表达式信息请参见第9.6.4节。在bash下,算术表达式的语法如下:

$((expression))

shell计算expression并用其计算结果代替$((expression))。这个语法类似于命令替换所用的语法($(...)),并将执行相同的功能。可将$((expression))作为参数传递给命令或者放置在命令行上任何数字位置上。

expression的构成规则与C编程语言的规则类似。所有标准C算术操作符都可用(如表11-8所示)。bash中的算术使用整数进行计算。然而,除非使用整数类型的变量或者真正的整数,shell必须将字符串值转换到整数,以用于算术计算。

不需要在expression中的变量名称前加上美元符号($)。下面的示例包含了一个判断距离60岁还有多少年的算术表达式:

$ cat age_check

#!/bin/bash

echo -n "How old are you? "

read age

echo "Wow, in $((60-age)) years, you'll be 60!"

$ age_check

How old are you? 55

Wow, in 5 years, you'll be 60!

不必将expression放在引号中,这是因为bash不对其进行文件名扩展。使用这个特性可以更加容易地使用星号(*)进行乘法运算,如下面的示例所示:

$ echo There are $((60*60*24*365)) seconds in a non-leap year.

There are 31536000 seconds in a non-leap year.

 

使用较少的美元符号($) 

提示

如果在$((和))中使用变量,那么单个变量引用前面的美元符号就是可选的:

$ x=23 y=37

$ echo $((2*$x + 3*$y))

157

$ echo $((2*x + 3*y))

157

下面的示例使用工具wc、cut、算术表达式和命令替换来计算打印文件letter.txt内容所需要的页数。如果带上-l选项,那么wc工具的输出内容为该文件的行数(从第1栏到第4栏),后面跟着一个空格符以及该文件的文件名(第1条命令后面跟着的文件)。cut工具的选项-c1-4将提取前4栏内容。

$ wc -1 letter.txt

351 letter.txt

$ wc -1 letter.txt | cut -cl-4

351

美元符号和单个圆括号指示shell执行命令替换,而美元符号和两个圆括号指示shell进行算术扩展:

$ echo $(( $(wc -1 letter.txt | cut -cl-4)/66 + 1))

6

在这个示例中,wc的标准输出通过管道发送给cut的标准输入。进行命令替换后,两条命令的输出替换掉该命令行中介于“$(”和匹配“)”中间的命令。然后在算术扩展中,将该数字除以每页的行数,即66。表达式末尾加上1,这是因为整除将丢弃余数。

还有一种不使用cut的方法可以得到同样的结果,就是重定向wc的输入,而不让wc从命令行中指定的文件中获取输入。如果重定向wc的输入,它就不会显示该文件的文件名:

$ wc -1 < letter.txt

351

一种常见的用法是将算术扩展结果赋值给某个变量:

$ numpages=$(( $(wc -1 < letter.txt)/66 + 1))

内置命令let  内置命令let(在tcsh中不可用)计算算术表达式的值,与$(( ))语法类似。下面的命令与前面那个示例等价:

$ let "numpages=$(wc -1 < letter.txt)/66 + 1"

双引号的作用是阻止空白符(包括用户可见的空白符以及命令替换导致的空白符)将该表达式分隔成单个的参数传递给let。最后那个表达式的结果将决定let的退出状态。如果最后那个表达式的值为0,那么let的退出状态为1;否则退出状态为0。

可在同一个命令行上给let传递多个参数:

$ let a=S+3 b=7+2

$ echo $a $b

8 9

当用let或者$(( ))进行算术扩展的时候,如果要引用变量,shell不需要在变量名前加上美元符号($)。尽管如此,这仍然是一个很好的习惯,因为在大多数情况下,必须包含这个符号。

6. 命令替换

命令替换指的是用命令的输出来代替该命令。在bash下面,命令替换的优选语法如下:

$(command)

在bash下面,还可以使用下面的语法。该语法是在tcsh下面的唯一选择:

` command`

shell在子shell中执行command,然后用command的标准输出取代command,连同左右两边的标点。

在下面的示例中,shell执行pwd并用该命令的输出替换该命令及其两边的标点。然后shell将该命令的输出作为一个参数传递给echo,echo将其显示出来。

$ echo $(pwd)

/home/alex

下面的脚本将内置命令pwd的输出赋值给变量where,并显示一条包含该变量值的消息:

$ cat where

where=$(pwd)

echo "You are using the Swhere directory."

$ where

You are using the /home/jenny directory.

这个示例没有什么实际意义,只是为了演示如何将一条命令的输出赋值给一个变量。不使用变量就可以直接显示pwd的输出:

$ cat where2

echo "You are using the $(pwd) directory."

$ where2

You are using the /home/jenny directory.

下面的命令用find在以工作目录为根的目录树中查找名为README的文件。文件列表为find的标准输出,并成为ls的参数列表。

$ ls -1 $(find . -name README -print)

下面的这个示例演示了旧的`command`语法:

$ ls –l `find . –name README –print`

新语法的一个优点是避免了旧语法中用于标志处理、引号处理以及关于字符(`)等晦涩规则。新语法的另一个优点是它可以嵌套,这点与旧语法不同。举例来说,使用下面的命令,可以生成一个所有README文件的列表,这些文件所占空间都要大于./README:

$ ls –l $(find . –name README –size +$(echo $(cat ./README | wc -c)c ) –print )

试着在执行set -x命令之后再次执行这个命令,看看bash如何扩展这条命令。如果没有README文件,将会得到ls -l的输出。

可在第11.1.3节、第11.1.9节和第11.4.4节中找到更多使用命令替换的脚本。

 

$((与$( 

提示

符号$((构成了一个单独的标志。它们引入一个算术表达式,而不是命令替换。因此,如果想在$( )中使用一个被插入的子shell,就必须在$(和下一个(之间插入一个空格符。

7. 分词

参数和变量扩展、命令替换和算术扩展的结果都可以作为分词的候选者。bash使用IFS中的每个字符作为可能的分隔符,将这些候选者划分成字或者标记。如果没有设置IFS,bash会使用它的默认值(空格符、制表符和换行符)。而如果IFS为空,bash就不进行分词。

8. 路径名扩展

路径名扩展又称为文件名生成或者文件名补全,它表示解释模糊文件引用以及替换合适文件名的列表的过程。除非设置了noglob标记,否则当shell遇到模糊文件引用(这个标记包含未被引用字符*、?、[或者]中的任何一个)时,它将执行这项功能。如果bash找不到任何匹配该模式的文件,那么带有该模糊文件引用的标记将被保留下来。shell并不会将这个字删除或者用空串取代,而是将其原封不动地传递给程序(例外请参见nullglob)。TC Shell会产生一条错误消息。

在下面的示例中的第1条echo命令中,shell扩展了模糊文件引用tmp*,并将3个字(tmp1、tmp2以及tmp3)传递给echo。内置命令echo显示shell传递给它的3个文件名。在rm删除这3个tmp*文件之后,shell在扩展tmp*时发现没有文件名可以匹配tmp*。于是,shell将未扩展的字符串传递给内置命令echo。echo将传递给它的这个字符串显示出来。

$ ls

tmpl tmp2 tmp3

$ echo tmp*

tmp1 tmp2 tmp3

$ rm tmp*

$ echo tmp*

tmp*

默认情况下,同样的命令将使TC Shell显示一条错误消息:

tcsh $ echo tmp*

echo: No match

无论是在路径名首位,还是跟在路径名中的某个斜杠(/)后面,句点都必须显式地进行匹配,除非设置了dotglob。选项nocaseglob使模糊文件引用在匹配文件名时不必考虑大小写。

引号  用双引号把参数引起来将使shell抑制路径名扩展和所有除参数扩展和变量扩展之外的其他扩展。而用单引号把参数引起来会抑制所有类型的扩展。下面这个示例中的第2条echo命令显示双引号之间的变量$alex,这时候是允许进行变量扩展的。结果就是shell将这个变量扩展为它的值:sonar。第3条echo命令中并不会出现这种扩展,这是因为它使用了单引号。因为无论是单引号还是双引号都不允许进行路径名扩展,所以最后两条命令显示了未经扩展的参数tmp*。

$ echo tmp* $a1ex

tmp1 tmp2 tmp3 sonar

$ echo "tmp* $a1ex"

tmp* sonar

$ echo 'tmp* Salex'

tmp* $a1ex

shell区分变量的值和该变量的引用,如果模糊文件引用出现在变量的值中,shell不会扩展它们。因而,可以将一个包含特殊字符如星号(*)的值赋值给变量。

扩展级别  在下面对示例中,工作目录下面有3个名字以letter开头的文件。如果将值letter*赋值给变量var,因为这个模糊文件引用出现在变量的值中(在该变量的赋值语句中),所以shell并不会将其扩展。在字符串letter*左右没有引号,只有上下文阻止了该扩展。在赋值之后,内置命令set(在grep的辅助下)显示var变量的值为letter*。

下面的3条echo命令演示了3种级别的扩展。如果$var被单引号引用,shell不执行任何扩展并将字符串$var传递给echo显示出来。如果使用双引号,shell只执行变量扩展并用var的值替换掉变量名var以及前面那个美元符号。没有进行路径名扩展,这是因为双引号抑制了这种扩展。在最后那条命令中,因为没有双引号的限制,shell执行变量替换,然后在将参数传递给echo之前,执行路径名扩展。

$ ls letter*

letter1 1etter2 1etter3

$ var=1etter*

$ set | grep var       

var='letter*'

$ echo '$var'

$var

$ echo "$var"

letter*

$ echo $var

letter1 1etter2 1etter3

9. 进程替换

Bourne Again Shell的一项特殊功能是能够用进程替换文件名参数。使用语法<(command)的参数将使command得以执行,同时其结果被写入到某个命名管道(FIFO)。shell将用这个管道的名字替换掉这个参数。如果在处理期间,将这个参数用作某个输入文件的名称,那么command的输出将被读取。类似地,使用语法>(command)的参数将被command作为标准输入的管道的名字所代替。

下面的示例使用带-m选项(merge,合并,只有在输入文件已经排序的前提下这个选项才能正常工作)的sort将两个单词列表合并为一个表。每个单词列表都是通过管道提取匹配某个模式的单词而产生的,并在该列表中进行排序。

$ sort -m -f <(grep "(^A-Z)..$" memol | sort) <(grep ".*aba.*" memo2 |sort)

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论