shell脚本程序内部执行的两类命令:
1、可以在命令提示符中执行的“普通”命令,也称为外部命令(external command)
2、“内置”命令,也称为内部命令(internalcommand)
内置命令是在shell内部实现的,不能作为外部程序被调用,然而大多数的内部命令同时也提供了独立运行的程序版本(POSIX规范的需求)。通常情况下,内部命令的执行效率更高。
【命令】
1、break命令
在控制条件未满足之前,跳出for、while或until循环,可以为break命令提供一个额外的数值参数来表明需要跳出的循环层数,在默认情况下,break只跳出一层循环。
#!/bin/bash
rm –rf fred*
echo > fred1
echo > fred2
mkdir fred3
echo > fred4
for file in fred*
do
if [ -d “$file” ]
then
break
fi
done
echo “first directory starting fred was$file”
rm –rf fred*
exit 0
2、:命令
冒号(:)命令是一个空命令,可被用于简化条件逻辑,相当于true的一个别名,由于是内置命令,所以运行的比true快。
while :实现了一个无限循环,代替了更常见的whiletrue。
:结构用在变量的条件设置中(暂不明):
: ${var:=value}
例子:
#!/bin/bash
rm –f fred
if [ -f fred]
then
:
else
echo ‘file fred did not exist’
fi
exit 0
3、continue命令
使for、while或until循环跳到下一次循环继续执行,循环变量取循环列表中的下一个值。
#!/bin/bash
rm –rf fred*
echo > fred1
echo > fred2
mkdir fred3
echo > fred4
for file in file*
do
if [ -d “$file” ]
then
echo “skipping directory $file”
continue
fi
echo “file is $file”
done
rm –rf fred*
exit 0
continue可以带一个可选的参数以表示希望继续执行的循环嵌套层数:
for x in 1 2 3
do
echo before $x
continue 1
echo after $x
done
输出:
before 1
before 2
before 3
4、.命令
点(.)命令用于在当前shell中执行命令:
. ./shell_script
通常,当一个脚本执行一条外部命令或脚本程序时,它会创建一个新的环境(一个子shell),命令将在这个新环境中执行,在命令执行完毕后,这个环境被丢弃,留下退出码返回给父shell。但外部的source命令和点命令(同义)在执行脚本程序中列出的命令时,使用的是调用该脚本程序的同一个shell。
因为在默认情况下,shell脚本程序会在一个新创建的环境中执行,所以脚本程序对环境变量所作的任何修改都会丢失。而点命令允许执行的脚本程序改变当前环境。当要把一个脚本当作“包裹器”来为后续执行的一些其他命令设置环境时,通常使用这个命令。
在shell脚本程序中,可以使用点命令将变量和函数定义结合进脚本程序。
使用环境设置文件设置不同的开发环境:
##classic_set
#!/bin/bash
version=classic
PATH=/usr/local/old_bin:/usr/bin:/bin:.
PS1=’classic> ’
##latest_set
#!/bin/bash
version=latest
PATH=/usr/local/new_bin:/usr/bin:/bin:.
PS1=’latest version> ’
使用:
$ . ./classic_set
classic> echo $version
classic
classic> . /latest_set
latest version> echo $version
latest
latest version>
脚本程序使用点命令执行,所以每个脚本程序都是在当前shell中执行。这使得脚本程序可以改变当前shell中的环境设置,即使脚本程序执行结束后,改变仍然有效。
5、echo命令
去掉输出的换行符:
echo –n ‘string to output’
或者:
echo –e ‘string to output\c’
第二种方法echo –e确保启用了反斜线转义字符(\c——去掉换行符,\t——制表符,\n——回车)的解释。老版本bash默认启用,新版本默认不启用。
如果需要脚本兼容Unix系统并且需要删除换行符,最好使用printf命令。
6、eval命令
eval允许对参数进行求值,是shell的内置命令,通常不会以单独命令的形式存在。
foo=10
x=foo
y=’$’$x
echo $y
输出:
$foo
而:
foo=10
x=foo
eval y=’$’$x
echo $y
输出:
10
eval命令像一个额外的$,它给出一个变量的值的值,允许代码被随时生成和运行。
7、exec命令
exec的典型用法是将当前shell替换为一个不同的程序:
exec wall ‘paramters’
使用wall命令替换当前的shell,exec命令后面的代码都不会执行,因为执行这个脚本的shell已经不存在。
exec第二种用法是修改当前文件描述符:
exec 3< afile
这使得文件描述符3被打开以便从文件afile中读取数据(用法少见)。
8、exit n命令
exit命令使脚本程序以退出码n结束运行,在交互式命令提示符中使用则退出系统。如果脚本程序在退出时不指定一个退出状态,则脚本的最后一条被执行命令的状态将被用作返回值。
在shell脚本编程中,退出码0表示成功,退出码1~125是脚本程序可以使用的错误代码,其余数字具有保留含义。
|
|
|
|
|
|
|
|
.profile在当前目录下存在就返回0表示成功:
#!/bin/bash
if [ -f .profile ]
then
exit 0
fi
exit 1
如果需要更简洁,则可以结合AND和OR列表进行重写:
[ -f .profile] && exit 0 || exit 1
9、export命令
export命令将作为它参数的变量导出到子shell中,并使之在后续的子shell中有效。在默认情况下,在一个shell中被创建的变量在这个shell调用的下级(子)shell中是不可用的。export命令把自己的参数创建为一个环境变量,而这个环境变量可以被当前程序调用的其他脚本和程序可见,技术上说,被导出的变量构成从该shell衍生的任何子进程的环境变量。
导出变量:
##export2
#!/bin/bash
echo “$foo”
echo “$bar”
##export1
foo=’The first meta-syntactic variable’
export bar=’The second meta-syntacticvariable’
bash export2
执行输出:
$ ./export1
The second meta-syntactic variable
$
一旦一个变量被shell导出,它就可以被该shell调用的任何脚本使用,也可以被后续依次调用的任何shell使用。如果脚本export2调用了另一个脚本,bar的值对新脚本来说仍然有效。
set –a或set –allexport命令将导出它之后声明的所有变量。
10、expr命令
expr命令将它的参数当作一个表达式来求值。
简单数学运算:
x=`expr $x+1`
反引号(“)字符使x取值为命令expr $x+1的执行结果。
可以使用语法$()替换反引号:
x=$(expr $x+1)
expr可以完成很多表达式求值计算:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
在较新的脚本程序中,expr命令通常被替换为更有效的$((…))语法。
11、printf命令
只有新版本的shell才提供printf命令。
printf “format string” parameter1parameter2 …
printf命令不支持浮点数,因为shell中所有的算术运算都是按照整数来进行计算的,格式字符串由各种可打印字符、转义序列和字符转换限定符组成,除了%和\之外的所有字符都将按原样输出。
12、return命令
使函数返回,如果有数值参数,则在调用该函数的脚本程序里被看做是函数返回值,如果没有指定参数,return默认返回最后一条命令的退出码。
13、set命令
为shell设置参数变量,因为许多命令的输出结果是以空格分隔的值,使用set便于使用结果中的某个域。
获取当前月份:
#!/bin/bash
echo the date is $(date)
set $(date)
echo The month is $2
exit 0
把date命令的输出设置为参数列表,通过位置参数$2获取月份。
date命令输出受本地语言影响,可以使用date+%B来提取月份名字。
可以通过set命令和其参数来控制shell的执行方式,比如set –x允许脚本程序跟踪显示当前执行的命令。
14、shift命令
把所有参数变量左移一个位置($2变成$1,$3变成$2……,其中原来$1值被丢弃,$0任然保持不变)。
如果调用shift命令时指定数值参数,则所有的参数将左移指定次数,左移过程中,$*、$@和$#等其他变量也将根据参数变量的新安排做相应的变动。
扫描所有位置参数:
#!/bin/bash
while [ “$1” != ‘’ ]
do
echo “$1”
shift
done
exit 0
15、trap命令
用于指定在接收到信号后将要采取的行动,一种常见用途是在脚本程序被中断时完成清理工作。其中,信号是指那些被异步发送到一个程序的事件。
新版的bash中,使用信号名时SIG前缀是可选的,可以在命令提示符下输入命令trap –l来查看信号编号及其关联的名称。
trap命令有两个参数:第一个参数是接收到指定信号时将要采取的行动,第二个参数是要处理的信号名:
trap command signal
如果要重置某个信号的处理方式到其默认值,只需将command设置为-。如果要忽略某个信号,就把command设置为空字符串’’。一个不带参数的trap命令将列出当前设置的信号及其行动的清单。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
信号处理:
#!/bin/bash
trap “rm –f /tmp/my_tmp_file_$$” SIGINT
echo “creating file /tmp/my_tmp_file_$$”
date > /tmp/my_tmp_file_$$
echo ‘press interrupt (CTRL-C) to interrupt…’
while [ -f /tmp/my_tmp_file_$$ ]
do
echo ‘File exists’
sleep 1
done
echo ‘The file no longer exists’
trap SIGINT
echo “creating file /tmp/my_tmp_file_$$”
date > /tmp/my_tmp_file_$$
echo ‘press interrupt (CTRL-C) to interrupt…’
while [ -f /tmp/my_tmp_file_$$ ]
do
echo ‘File exists’
sleep 1
done
echo ‘we never get here’
exit 0
第一个循环正常退出,第二个循环因指定INT信号出现时不执行任何命令而采取默认处理方式,即立即终止脚本程序。因为脚本程序被立即终止了,所以最后的echo和exit语句永远不会被执行。
16、unset命令
从环境中删除变量或函数,但是不能删除shell本身定义的只读变量(如IFS)。
#!/bin/bash
foo=’Hello World’
echo $foo
unset foo
echo $foo
第一次输出字符串,第二次输出换行符。
17、find命令
用于搜索文件,有选项、测试和动作类型的参数,其中一个参数的处理结果可能会影响后续参数的处理。
在本地机器上查找名为test的文件:
find / -name test –print
从根目录开始查找名为test的文件,并且输出文件的完整路径。
指定-mount选项,告诉find命令不要搜索挂载的其他文件系统的目录:
find / -mount –name test –print
find命令的语法格式:
find [path] [options] [tests] [actions]
path代表路径,可以使用绝对路径,也可以使用相对路径。
|
|
|
|
|
|
|
|
|
|
可以提供给find命令的测试非常多,每种测试返回true或false。find命令开始工作时,它按照顺序将定义的每种测试依次应用到它搜索到的每个文件上,如果一个测试返回false,find命令就停止处理它当前找到的这个文件,并继续搜索,如果一个测试返回true,find命令将继续下一个测试或对当前文件采取行动。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
可以用操作符来组合测试。
|
|
|
|
|
|
|
|
|
|
|
|
可以通过使用圆括号来强制测试操作符的优先级,由于圆括号对shell来说有特殊含义,所以必须使用反斜线来引用圆括号。
测试搜索的文件比文件X要新,或者文件名以下划线开头:
\( -newer X –o –name “_*”\)
在当前目录下搜索比文件while2要新的文件:
$ find . –newer while2 –print
结果中包括了当前目录,如果只需普通文件则增加额外测试:
$ find . –newer while2 –type f –print
查找以下划线开头的文件或比while2文件要新的普通文件:
$ find . \( -name “_*” –o –newer while2 \) –typef –print
-exec和-ok命令将命令行上后续的参数作为参数的一部分,直到被\;序列终止,魔术字符串{}是-exec或-ok命令的一个特殊类型的参数,它将被当前文件的完整路径取代。
|
|
|
|
|
|
|
|
|
|
例子:
$ find . –newer while2 –type f –exec ls –l{} \;
18、grep命令
grep,通用正则表达式解析器(GeneralRegular Expression Parser)。可以使用find命令在系统中搜索文件,而使用grep命令在文件中搜索字符串。一种常见的用法是在使用find命令时,将grep作为传递给-exec的一条命令。
grep命令使用选项、一个要匹配的模式和要搜索的文件:
grep [options] PATTERN [FILES]
如果没有提供文件名,则grep命令将搜索标准输入。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
例子:
$ grep in words.txt
$ grep –c in words.txt words2.txt
$ grep –c –v in words.txt words2.txt
第一个例子文件名未输出是因为只在一个文件中进行搜索。
19、正则表达式
被广泛应用于Linux和许多其他开源编程语言中。
在方括号中使用的特殊匹配模式:[:alnum:]、[:alpha:]、[:ascii:]、[:blank:]、[:cntrl:]、[:digit:]、[:graph:]、[:lower:]、[:print:]、[:punct:]、[:space:]、[:upper:]、[:xdigit:]。
查找以字母e结尾的行:
$ grep e$ words2.txt
查找以字母a结尾的单词:
$ grep a[[:blank:]] words2.txt
查找以Th开头的由3个字母组成的单词:
$ grep Th.[[:space:]] words2.txt
使用扩展grep模式来搜索只有10个字符长的全部由小写字母组成的单词:
$ grep –E [a-z]\{10\} words2.txt
【命令的执行】
执行一条命令,并把命令的输出放在一个变量中,可以使用$(command)语法来实现,也可以使用一种比较老的语法形式`command`。
所有的新脚本程序都应该使用$(…)形式,目的是为了避免在使用反引号执行命令时,处理其内部的$、`、\等字符所需要应用的相当复杂的规则。
$(command)的结果是命令的输出,不是命令的退出状态,而是字符串形式的输出结果。
#!/bin/bash
echo “The current directory is $PWD”
echo “The current users are $(who)”
exit 0
因为当前目录是一个shell环境变量,所以第一行不需使用这个命令执行结构。但是要获得who命令的输出结果,就需要使用这个结构。
把结果放在一个变量中:
whoisthere=$(who)
echo $whoisthere
如果需要把一条命令在标准输出上的输出转换为一组参数,并且将它们作为另一个程序的参数,需使用xargs命令(用法要参照手册)。
1、算术扩展
expr命令可以处理一些简单的算术命令,但执行起来相当慢,因为需要调用一个新的shell来处理expr命令。
更好的办法是使用$((…))扩展,把准备求值的表达式括在$((…))中能够更有效地完成简单的算术运算。
#!/bin/bash
x=0
while [ “$x” –ne 10 ]
do
echo $x
x=$(($x+1))
done
exit 0
这与x=$(…)命令不同,两对圆括号用于算术替换,一对圆括号用于命令的执行和获取输出。
2、参数扩展
处理名为1_tmp和2_tmp的两个文件:
#!/bin/bash
for i in 1 2
do
my_secret_process $i_tmp
done
shell试图替换变量$i_tmp的值,而这个变量其实并不存在,shell并不会认为这是一个错误,仅仅会将它替换为一个空值,因此根本不会有参数被传递给处理函数。
必须把i放在花括号中:
#!/bin/bash
for i in 1 2
do
my_secret_process ${i}_tmp
done
可以在shell中采用多种参数替换方法。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
参数的处理:
#!/bin/bash
unset foo
echo ${foo:-bar}
foo=fud
echo ${foo:-bar}
foo=/usr/bin/X11/startx
echo ${foo#*/}
echo ${foo##*/}
bar=/usr/local/etc/local/networks
echo ${bar%local*}
echo ${bar%%local*}
exit 0
输出:
bar
fud
usr/bin/X11/startx
startx
/usr/local/etc/
/usr/
如果语句是${foo:=bar},那么变量$foo会被赋值,它检查变量foo是否存在且不为空,如果它不为空,就返回它的值,否则就把变量foo赋值为bar并返回那个值。
${foo:?bar}语句将在变量foo不存在或它设置为空的情况下,输出foo:bar并异常终止脚本程序,最后${foo:+bar}语句将在变量foo存在且不为空的情况下返回bar。
因为Unix和Linux系统都非常依赖过滤器的思想,所以一个操作的结果常常必须手工重定向。
使用cjpeg程序将一个GIF文件转换为一个JPEG文件:
$ cjpeg image.gif > image.jpg
对大量文件执行自动重定向:
#!/bin/bash
for image in *.gif
do
cjpeg $image > ${image%%gif}jpg
done
脚本为当前目录中的每个GIF文件创建一个对应的JPEG文件。
【here文档】
here文档允许一条命令在获得输入数据时就好像在读取一个文件或键盘一样,而实际上是从脚本程序中得到输入数据。
here文档以两个连续的小于号<<开始,紧跟着一个特殊的字符序列,该序列将在文档的结尾处再次出现。<<是shell的标签重定向符,这里它强制命令的输入是一个here文档。这个特殊字符序列的作用就像一个标记,它告诉shell here文档结束的位置。因为这个标记序列不能出现在传递给命令的文档内容中,所以应该尽量使它既容易记忆又相当不寻常。
给cat命令提供输入数据:
#!/bin/bash
cat <<!FUNKY!
hello
this is a here
document
!FUNKY!
exit 0
<<与!之间的空格可选。
here文档功能可以用来调用交互式的程序,比如一个编辑器,并向其提供一些事先定义好的输入。
更常见的用途是在脚本程序中输出大量的文本(如上述例子),从而可以避免用echo语句来输出每一行,可以在标识符两端都使用感叹号(!)来确保不会引起混淆。
如果想按预定的方式处理一个文件中的几行,可以使用ed行编辑器,并在脚本程序中通过here文档向其提供命令。
假设a_text_file文件内容为:
That is line 1
That is line 2
That is line 3
That is line 4
结合here文档和ed编辑器来编辑:
#!/bin/bash
ed a_text_file << !FunkyStuff!
3
d
.,\$s/is/was/
w
q
!FunkyStuff!
exit 0
文件内容变为:
That is line 1
That is line 2
That was line 4
脚本程序调用ed编辑器并向其传递命令,先让其移动到第三行,然后删除该行,再把当前行的is替换为was。
【调试脚本程序】
脚本程序的调试并没有特定的工具,出现错误时,shell一般都会打印出包含错误的行的行号。如果错误不是非常明显,可以添加一些额外的echo语句来显示变量的内容,也可以通过在shell中交互式地输入代码片段来对它们进行测试。
因为脚本程序是解释执行的,所以在脚本程序的修改和重试过程中没有编译方面的额外开支。跟踪脚本程序中复杂错误的主要方法是设置各种shell选项——可以在调用shell时加上命令行选项,或是使用set命令。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
可以使用-o选项启用set命令的选项标志,用+o选项取消设置,对简写版本也一样。
在shell中,还可以通过捕获EXIT信号,从而在脚本程序退出时查看到程序的状态:
trap ‘echo Exiting: critical variable =$critical_variable’ EXIT