linux程序设计——shell程序设计(第二章)

这篇是shell的命令与命令的执行,文章中的命令在这里下载命令命令的执行,上一篇是shell中的变量,条件判断,控制结构和函数,上上篇介绍了什么是shell,管道和重定向,作为程序设计语言的shell

2.6.5    命令

在shell脚本程序内部可以执行两类命令。一类是可以在命令提示符中执行的"普通"命令,也成为外部命令,一类是"内置"命令,也成为内部命令。内置命令是在shell内部实现的,它们不能作为外部程序被调用。然后,大多数的内部命令同时也提供了独立运行的程序版本,这是POSIX规范的一部分。

1.break命令

在控制条件未满足之前,跳出for,while或者until循环。默认情况下,break只跳出一层循环。

2.:命令

冒号:命令是一个空命令,它偶尔会被用于简化条件逻辑,相当于true的一个别名。由于它是内置命令,所以运行的比true快,但它的输出可读性较差。例如可以使用while :实现一个无限循环,代替更常见的while true。

3.continue命令

这个命令使for、while或until循环跳到下一次循环继续执行,循环变量取循环列表中的下一个值。

4..命令

.命令用于当前shell中执行命令
../shell_script
通常,当一个脚本执行一条外部命令或脚本程序时,它会创建一个新的环境(一个子shell),命令将在这个新环境中执行,在命令执行完毕后,这个环境将被丢弃,留下退出码返回给父shell。但外部的source命令和点命令(这两个命令是同义词)在执行脚本程序中列出的命令时,使用的是调用该脚本程序的同一个shell。
因为在默认情况下,shell脚本程序会在一个新创建的环境中执行,所以脚本程序对环境变量所作的任何修改都会丢失。而点命令允许执行的脚本程序改变当前环境。如果要把一个脚本当做“包裹器”来为后续执行的一些其他命令设置环境时,这个命令通常就很有用。
在shell脚本程序中,点命令的作用有点类似与C或者C++中的#include指令,它在当前上下文中执行命令,可以使用它将变量和函数定义结合进脚本程序。

5.echo命令

虽然,X/Open建议在现在shell中使用printf命令,但是还是经常使用echo命令来输出结尾带有换行符的字符串。
一个常见的问题是如何去掉换行符,linux常用的解决方式为:echo -n "string to output"

6.eval命令

eval命令允许对参数进行求值,它是shell的内置命令,通常不会以单独命令的形式存在。
foo=10
x=foo
y=‘$‘$x
echo $y
foo=10
x=foo
eval y=‘$‘$x
echo $y
前一个结果输出$foo,后一个结果输出10。eval命令有点像额外的$,它给出一个变量的值的值
有点类似指针? $类似于*,取出地址所指向的内容。

7.exec命令

exec命令有两种不同的用法,它的典型用法是将当前shell替换为一个不同的程序。例如:
exec wall "Thanks for all the fish"
脚本中的这个命令会用wall命令替换当前的shell,脚本程序中exec命令后面的代码都不会执行,因为执行这个脚本的shell已经不存在了。

8.exit n命令

exit命令使脚本程序以退出码n结束运行。如果在任何一个交互式shell的命令提示符中使用这个命令,它会使退出系统。如果允许脚本程序退出时不指定一个退出状态,那么该脚本中最后一条被执行命令的状态将被用作返回值。在脚本程序中提供一个退出码总是一个良好的习惯。
在shell脚本编程中,退出码0表示成功。
下面是一个简单的例子,如果当前目录下存在一个名为.profile的文件,它返回0表示成功,返回1表示失败:
#!/bin/sh
if [ -f *.profile ]
then
    exit 0
fi
exit 1
如果追求更简洁的版本,则为:
[ -f *.profile ] && exit 0 || exit 1

9.export命令

export命令将作为它参数的变量导出到子shell中,并使之在子shell中有效。在默认情况下,在一个shell中被创建的变量在这个shell调用的下级shell中是不可用的。export命令把自己的参数创建为一个环境变量,而这个环境变量可以被当前程序调用的其他脚本和程序看见。从更技术的角度来说,被导出的变量构成从该shell衍生的任何子进程的环境变量。
一旦一个变量被shell导出,它就可以被该shell调用的任何脚本使用,也可以被后续依次调用的任何shell使用。

10.expr命令

expr命令将它的参数当作一个表达式来求值。它最常见的用法就是进行简单数学运算:
x=`expr $x + 1`(注意,这个命令貌似不能得到相应的结果,使用$()可以达到目的,后来发现反引号是Tab上面的键,不是单引号)
反引号`‘字符使x取值为命令expr $x + 1的执行结果。也可以使用语法$()替换反引号‘‘,如下所示:
x=$(expr $x + 1)
表达式求值                    说明
expr1 | expr2                如果expr1非零,则等于expr1,否则等于expr2
expr1 & expr2                只要有一个表达式为零,则等于零,否则等于expr1
在较新的脚本程序中,expr命令通常被替换为更有效的$((...))语法

11.printf命令

只有新版本的shell才提供printf命令,X/Open规范建议我们应该用它来代替echo命令,以产生格式化的输出。
printf "%s\n" hello
printf "%s %d\t%s" "hi There" 15 people

12.return命令

return命令的作用是使函数返回。return命令有一个数值参数,这个参数在调用该函数的脚本程序里被看做是该函数的返回值。如果没有指定参数,return命令默认返回最后一条命令的退出码。

13.set命令

set命令的作用是为shell设置参数变量。许多命令的输出结果是以空格分隔的值,如果需要使用输出结果中的某个域,这个命令就非常有用。
假设想在一个shell脚本中使用当前月份的名字。系统本身提供了一个date命令,它的输出结果中包含了字符串形式的月份名称,但是需要把它与其他区域分隔开。可以将set命令和$(...)结构相结合起来执行date命令,并且返回想要的结果。date命令的输出把月份字符串作为它的第二个参数:
#!/bin/sh
echo the date is $(date)
set $(date)
echo the month is $2
exit 0
这个程序把date命令的输出设置为参数列表,然后通过位置参数$2获得月份。
通过这个例子可以看到date命令怎样提取位置参数,由于date命令的输出受本地语言的影响较大,应该使用date +%B命令来提取月份名字。
可以通过set命令和它的参数来控制shell的执行方式,其中最常用的命令格式是set -x,它让一个脚本程序跟踪现实它当前执行的命令。

14.shift命令

shift命令把所有参数变量左移一个位置,使$2变成$1,$3变成$2,以此类推。原来$1的值将被丢弃,而$0保持不变。如果调用shift命令时指定了一个数值参数,则表示所有的参数将左移指定的次数。$*,$@和$#等其他变量也将根据参数变量的安排而作相应的变动。

15.trap命令

trap命令用于指定在接受到信号后将要采取的行动,trap命令的一种常见的用户是在脚本程序被中断时完成清理工作。历史上,shell总是用数字来代表信号,但新的脚本程序应该使用信号的名字,它们定义在头文件signal.h中,使用信号名时需要省略SIG前缀。可以在命令提示符下输入命令trap -l来查看信号编号及其关联的名称。
trap命令有两个参数,第一个参数是接收到指定信号时将要采取的行动,第二个参数是要处理的信号名。
trap command signal
脚本程序通常是从上到下的顺序解释执行的,因此必须在要保护的那部分代码之前指定trap命令。
信号是指那些被异步发送到一个程序的事件,在默认情况下,它们通常会终止一个程序的运行。
如果要重置某个信号的处理方式到其默认值,只需要将command设置为-。如果要忽略某个信号,就把command设置为空字符串‘‘。一个不带参数的trap命令将列出当前设置的信号以及其行动的清单。
信号                说明
HUP(1)            挂起,通常因终端掉线或用户退出而引发
INT(2)            中断,通常因按下Ctrl+C组合键而引发
QUIT(3)            退出,通常因按下Ctrl+\组合健而引发
ABRT(6)            终止,通常因某些严重的执行错误而引发
ALRM(14)        报警,通常用来处理超时
TERM(15)        中之,通常在系统关机时发送

16.unset命令

unset命令的作用是从环境中删除变量或函数,这个命令不能删除shell本身定义的只读变量(如IFS)

17.另外两个有用的命令和正则表达式

这两个命令虽然不是shell的一部分,但是在shell程序时经常用到。同时介绍正则表达式,一种出现在所有linux以及与之相关联程序中的模式匹配特征。
find命令
find命令用于搜索文件。
find / -name test -print
这个命令从根目录开始查找文件名为test的文件,并且输出该文件的完整路径。
然后,这个命令的执行需要花费很长时间,并且网络上的Windows机器的硬盘也会高速转动。这是因为linux机器挂载了一大块Windows机器的文件系统,看起来似乎是Windows文件系统也被搜索了,尽管我们知道要查找的文件应该在Linux机器上。
这就是第一个选项发挥作用的时候了,如果指定-mount选项,就可以告诉find命令不要搜索挂载的其他文件系统目录。这样搜索速度会更快。
find命令的完整语法格式如下所示:
find [path] [options] [tests] [actions]
path部分:既可以使用绝对路径,如/bin,也可以使用相对路径,如.。如果需要,也可以指定多个路径,如find /var /home。
find命令有许多选项可用,如:
选项                        含义
-depth                    在查看目录本身之前先搜索目录的内容
-follow                    跟随符号链接
-maxdepths N            最多搜索N层目录
-mount(或-xdev)            不搜索其他文件系统中的目录
测试                        含义
-type c                    文件的类型为c,最常见的是d(目录)和f(普通文件)
操作符                    含义
!                        -not测试取反
-a                        -and两个测试都必须为真
-o                        -or两个测试有一个必须为真
在当前目录下搜索比文件test28更晚的文件:
$ find . -newer test28 -print
可以发现结果中还包含了当前目录,如果只想要普通文件,则增加一个额外的测试-type f
$ find . -newer test28 -type f -print
可以组合条件搜索,查找当前目录包含_的文件或者比test28更晚的文件
$ find . \( -newer test28 -o -name "*_*" \) -type f -print
由于圆括号对shell有特殊的含义,因此必须使用反斜线来引用圆括号。
grep命令
这个名字代表的是通用正则表达式解析器(General Regular Expresssion Parser,简写为grep),使用grep命令在文件中搜索字符串。一种常见的用法是在使用find命令时,将grep作为传递给-exec的一条命令。
grep命令使用一个选项,一个要匹配的模式和要搜索的文件,它的语法如下所示:
grep [options] pattern [files]
如果没有提供文件名,则grep命令将搜索标准输入。
选项                含义
-c                输出匹配行的数目,而不是输出匹配的行
-E                启用扩展表达式
-h                取消每个输出行的普通前缀,即匹配查询模式的文件名
-i                忽略大小写
-l                只列出包含匹配行的文件名,而不输出真正的匹配行
-v                对匹配模式取反,即搜索不匹配行而不是匹配行
例如:
$ grep in test1
显示test1中包含in的行

$ grep -c in test1 test2 test3
显示test1,test2,test3中包含in的行数
$ grep -c -v in test1 test2
显示test1,test2中不包含in的行数
正则表达式
正则表达式中最常用的特殊字符如下所示:
字符            含义
^            指向一行的开头
$            指向一行的结尾
.            任意单个字符
[]            方括号内包含一个字符范围,其中任何一个字符都可以被匹配,例如a~e,或
            在字符范围前面加上^表示使用反向字符范围。

在方括号中还可以使用一些有用的特殊匹配模式,如下所示:
匹配模式                    含义
[:alnum:]                字母与数字字符
[:alpha:]                字母
[:ascii:]                ASCII字符
[:blank:]                空格或者制表符
[:cntrl:]                ASCII控制字符
[:digit:]                数字
[:graph:]                非控制,非空格字符
[:lower:]                小写字母
[:print:]                可打印字符
[:punct:]                标点符号字符
[:space:]                空白字符
[:upper:]                大写字母
[:xdigit:]                十六进制数字
例如:
$ grep e$ test1

查找test1中以e为结尾的行
$ grep ^e test1 test2
查找显示test1,test2中以e为开头的行
$ grep a[[:blank:]] test1 test2

显示test1,test2中以e为结尾的单词
$ grep th.[[:space:]] test1 test2 test3
显示test1,test2,test3中以th开头的3个字母组成的单词,用字符.来匹配一个额外的字符
$ grep -E [a-z]\{10\} test1 test2 test3
查找显示test1,test2,test3中只有10个字符长的全部由小写字母组成的单词。

2.6.6 命令的执行

编写脚本程序时,需要捕获一条命令的执行结果,并把它用在shell脚本程序中。即执行一条命令,并把命令的输出放到一个变量中
所有的新脚本程序都应该使用$(...)形式$(command)的结果就是其中命令的输出。注意,这不是该命令的退出状态,而是它的字符串形式的输出结果。例如:
echo The current directory is $PWD
echo The current users are $(who)
如果要将命令的结果放到一个变量中,可以按照通常的方法来给它赋值,如下所示:
value=$(who)
echo $value
这种把命令的执行结果放到变量中的能力是非常强大的,它使得在脚本程序中使用现有命令并捕获其输出变得很容易。如果需要把一条命令在标准输出上的输出转换为一组参数,并且将它们用做为另一个程序的参数,命令xargs可以完成这个任务。

1.算数扩展

之前介绍expr命令可以处理一些简单的算数命令,但是这个命令执行起来相当慢,因为它需要调用一个新的shell来处理expr命令。
一个更新更好的办法是使用$((...))扩展。把准备求值的表达式括在$((...))中能够更有效地完成简单的算数运算。如下所示:
x=0
while [ "$x" -ne 10 ]
do
    echo $x
    x=$(($x+1))
done
注意,这与x=$(...)命令不同,两对圆括号用于算数替换,而我们之前见到的一对圆括号用于 命令的执行和获取输出。
???疑惑的是把x当做字符串处理判断条件,也得到相同的输出( "$x" != 10 )

2.参数扩展

之前介绍过简单的参数赋值和扩展了,如下所示:
foo=fred
echo $foo
???疑惑 shell中的赋值,有点指针传递的感觉,foo变量中存储fred的指针,取地址运算符*类似与$,获得变量的内容
如果想编写一个脚本程序,来处理test1和test2两个文件,可能这样写:
for i in 1 2
do
    ./testi
done
在每次循环中,发现./testi not found
问题在于./testi变量无法被替换,为了保护变量名中类似于$i部分的扩展,需要把i放在花括号中,如下所示
.test${i}
这样,每次循环中,变量i的值替换为$(i),给出正确的文件名,把参数的值替换进了一个字符串。
可以在shell中采用多种参数替换方法,对于多参数处理问题来说,这些方法通常提供了一种精巧的解决方案。
参数扩展            说明
${param:-default}    如果param为空,就把它设置为default的值
#{#param}            给出param的长度
${param%word}        从param的尾部开始删除与word匹配的最小部分,然后返回剩余部分
${param%%word}        从param的尾部开始删除与word匹配的最长部分,然后返回剩余部分
${param#word}        从param的头部开始删除与word匹配的最小部分,然后返回剩余部分
${param##word}        从param的头部开始删除与word匹配的最长部分,然后返回剩余部分
当处理字符串时,这些替换通常是非常有用的,特别是对字符串进行部分删除的最后4个参数扩张方法,在处理文件名和路径时非常有用。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。