Shell本身是一个用C语言编写的程序它是用户使用Linux的桥梁。Shell既是一种命令
语言又是一种程序设计语言。作为命令语言它交互式地解释和执行用户输入的命令作
为程序设计语言它定义了各种变量和参数并提供了许多在高级语言中才具有的控制结构
包括循环和分支。它虽然不是Linux系统核心的一部分但它调用了系统核心的大部分功能
来执行程序、建立文件并以并行的方式协调各个程序的运行。因此对于用户来说shell
是最重要的实用程序深入了解和熟练掌握shell的特性极其使用方法是用好Linux系统
的关键。可以说shell使用的熟练程度反映了用户对Linux使用的熟练程度。
一、什么是shell
当一个用户登录Linux系统之后系统初始化程序init就为每一个用户运行一个称为shel
l(外壳)的程序。那么shell是什么呢确切一点说shell就是一个命令行解释器它为
用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序用户可以用shell
来启动、挂起、停止甚至是编写一些程序。
当用户使用Linux时是通过命令来完成所需工作的。一个命令就是用户和shell之间对话的
一个基本单位它是由多个字符组成并以换行结束的字符串。shell解释用户输入的命令
就象DOS里的command.com所做的一样所不同的是在DOS中command.com只有一个
而在Linux下比较流行的shell有好几个每个shell都各有千秋。一般的Linux系统都将
bash作为默认的shell。
二、几种流行的shell
目前流行的shell有ash、bash、ksh、csh、zsh等你可以用下面的命令来查看你自己的
shell类型
#echo $SHELL
$SHELL是一个环境变量它记录用户所使用的shell类型。你可以用命令
#shell-name
来转换到别的shell这里shell-name是你想要尝试使用的shell的名称如ash等。这
个命令为用户又启动了一个shell这个shell在最初登录的那个shell之后称为下级的
shell或子shell。使用命令
$exit
可以退出这个子shell。
使用不同的shell的原因在于它们各自都有自己的特点下面作一个简单的介绍
1.ash
ash shell是由Kenneth Almquist编写的Linux中占用系统资源最少的一个小shell它
只包含24个内部命令因而使用起来很不方便。
2.bash
bash是Linux系统默认使用的shell它由Brian Fox和Chet Ramey共同完成是Bourne
Again Shell的缩写内部命令一共有40个。Linux使用它作为默认的shell是因为它有
诸如以下的特色
(1)可以使用类似DOS下面的doskey的功能用方向键查阅和快速输入并修改命令。
(2)自动通过查找匹配的方式给出以某字符串开头的命令。
(3)包含了自身的帮助功能你只要在提示符下面键入help就可以得到相关的帮助。
3.ksh
ksh是Korn shell的缩写由Eric Gisin编写共有42条内部命令。该shell最大的优
点是几乎和商业发行版的ksh完全兼容这样就可以在不用花钱购买商业版本的情况下尝试
商业版本的性能了。
4.csh csh是Linux比较大的内核它由以William Joy为代表的共计47位作者编成共有52个
内部命令。该shell其实是指向/bin/tcsh这样的一个shell也就是说csh其实就是tc
sh。
5.zch
zch是Linux最大的shell之一由Paul Falstad完成共有84个内部命令。如果只是一
般的用途是没有必要安装这样的shell的。
3. shell程序设计(基础部分)
其实作为命令语言交互式地解释和执行用户输入的命令只是shell功能的一个方面shell
还可以用来进行程序设计它提供了定义变量和参数的手段以及丰富的程序控制结构。使用
shell编程类似于DOS中的批处理文件称为shell script又叫shell程序或shell命令
文件。
1.shell基本语法
shell的基本语法主要就是如何输入命令运行程序以及如何在程序之间通过shell的一些参
数提供便利手段来进行通讯。
(1)输入输出重定向
在Linux中每一个进程都有三个特殊的文件描述指针标准输入(standard input文件
描述指针为0)、标准输出(standard output文件描述指针为1)、标准错误输出(standar
d error文件描述指针为2)。这三个特殊的文件描述指针使进程在一般情况下接收标准输
入终端的输入同时由标准终端来显示输出Linux同时也向使用者提供可以使用普通的文
件或管道来取代这些标准输入输出设备。在shell中使用者可以利用“>”和“<”来进行
输入输出重定向。如
command>file将命令的输出结果重定向到一个文件。
command>&file将命令的标准错误输出一起重定向到一个文件。
command>>file将标准输出的结果追加到文件中。
command>>&file将标准输出和标准错误输出的结构都追加到文件中。
command
(2)管道pipe
pipe同样可以在标准输入输出和标准错误输出间做代替工作这样一来可以将某一个程
序的输出送到另一个程序的输入其语法如下
command1| command2[| command3...]
也可以连同标准错误输出一起送入管道
command1| &command2[|& command3...]
(3)前台和后台
在shell下面一个新产生的进程可以通过用命令后面的符号“”和“&”来分别以前台
和后台的方式来执行语法如下
command
产生一个前台的进程下一个命令须等该命令运行结束后才能输入。
command &
产生一个后台的进程此进程在后台运行的同时可以输入其他的命令。
2。shell程序的变量和参数
像高级程序设计语言一样shell也提供说明和使用变量的功能。对shell来讲所有变量
的取值都是一个字符串shell程序采用$var的形式来引用名为var的变量的值。
Shell有以下几种基本类型的变量
(1)shell定义的环境变量 shell在开始执行时就已经定义了一些和系统的工作环境有关的变量这些变量用户还可以
重新定义常用的shell环境变量有
HOME用于保存注册目录的完全路径名。
PATH用于保存用冒号分隔的目录路径名shell将按PATH变量中给出的顺序搜索这些目
录找到的第一个与命令名称一致的可执行文件将被执行。
TERM终端的类型。
UID当前用户的标识符取值是由数字构成的字符串。
PWD当前工作目录的绝对路径名该变量的取值随cd命令的使用而变化。
PS1主提示符在特权用户下缺省的主提示符是“#”在普通用户下缺省的主提示符
是“$”。
PS2在shell接收用户输入命令的过程中如果用户在输入行的末尾输入“\”然后回车
或者当用户按回车键时shell判断出用户输入的命令没有结束时显示这个辅助提示符提
示用户继续输入命令的其余部分缺省的辅助提示符是“>”。
(2)用户定义的变量
用户可以按照下面的语法规则定义自己的变量
变量名=变量值
要注意的一点是在定义变量时变量名前不应加符号“$”在引用变量的内容时则应在
变量名前加“$”在给变量赋值时等号两边一定不能留空格若变量中本身就包含了空
格则整个字符串都要用双引号括起来。
在编写shell程序时为了使变量名和命令名相区别建议所有的变量名都用大写字母来表
示。
有时我们想要在说明一个变量并对它设定为一个特定值后就不在改变它的值这可以用下面
的命令来保证一个变量的只读性
readly 变量名
在任何时候建立的变量都只是当前shell的局部变量所以不能被shell运行的其他命令
或shell程序所利用export命令可以将一局部变量提供给shell执行的其他命令使用
其格式为
export 变量名
也可以在给变量赋值的同时使用export命令
export 变量名=变量值
使用export说明的变量在shell以后运行的所有命令或程序中都可以访问到。
(3)位置参数
位置参数是一种在调用shell程序的命令行中按照各自的位置决定的变量是在程序名之后
输入的参数。位置参数之间用空格分隔shell取第一个位置参数替换程序文件中的$1第
二个替换$2依次类推。$0是一个特殊的变量它的内容是当前这个shell程序的文件名
所以$0不是一个位置参数在显示当前所有的位置参数时是不包括$0的。
(4)预定义变量
预定义变量和环境变量相类似也是在shell一开始时就定义了的变量所不同的是用户
只能根据shell的定义来使用这些变量而不能重定义它。所有预定义变量都是由$符和另
一个符号组成的常用的shell预定义变量有
$#位置参数的数量
$*所有位置参数的内容
$?命令执行后返回的状态
$$当前进程的进程号 $!后台运行的最后一个进程号
$0当前执行的进程名
其中“$?”用于检查上一个命令执行是否正确(在Linux中命令退出状态为0表示该命
令正确执行任何非0值表示命令出错)。
“$$”变量最常见的用途是用作临时文件的名字以保证临时文件不会重复。
(5)参数置换的变量
shell提供了参数置换能力以便用户可以根据不同的条件来给变量赋不同的值。参数置换的
变量有四种这些变量通常与某一个位置参数相联系根据指定的位置参数是否已经设置类
决定变量的取值它们的语法和功能分别如下。
a. 变量=${参数-word}如果设置了参数则用参数的值置换变量的值否则用word置换。
即这种变量的值等于某一个参数的值如果该参数没有设置则变量就等于word的值。
b. 变量=${参数=word}如果设置了参数则用参数的值置换变量的值否则把变量设置成
word然后再用word替换参数的值。注意位置参数不能用于这种方式因为在shell程序
中不能为位置参数赋值。
c. 变量=${参数word}如果设置了参数则用参数的值置换变量的值否则就显示word
并从shell中退出如果省略了word则显示标准信息。这种变量要求一定等于某一个参
数的值如果该参数没有设置就显示一个信息然后退出因此这种方式常用于出错指示。
d. 变量=${参数+word}如果设置了参数则用word置换变量否则不进行置换。
所有这四种形式中的“参数”既可以是位置参数也可以是另一个变量只是用位置参数的
情况比较多。
四、shell程序设计的流程控制
和其他高级程序设计语言一样shell提供了用来控制程序执行流程的命令包括条件分支
和循环结构用户可以用这些命令建立非常复杂的程序。
与传统的语言不同的是shell用于指定条件值的不是布尔表达式而是命令和字符串。
1.test测试命令
test命令用于检查某个条件是否成立它可以进行数值、字符和文件三个方面的测试其
测试符和相应的功能分别如下
(1)数值测试
-eq等于则为真
-ne不等于则为真
-gt大于则为真
-ge大于等于则为真
-lt小于则为真
-le小于等于则为真
(2)字符串测试
=等于则为真
!=不相等则为真
-z 字符串字符串长度伪则为真
-n 字符串字符串长度不伪则为真
(3)文件测试
-e 文件名如果文件存在则为真
-r 文件名如果文件存在且可读则为真
-w 文件名如果文件存在且可写则为真 -x 文件名如果文件存在且可执行则为真
-s 文件名如果文件存在且至少有一个字符则为真
-d 文件名如果文件存在且为目录则为真
-f 文件名如果文件存在且为普通文件则为真
-c 文件名如果文件存在且为字符型特殊文件则为真
-b 文件名如果文件存在且为块特殊文件则为真
另外Linux还提供了与(“”)、或(“-o)、非(“-a”)三个逻辑操作符用于将测试条件
连接起来其优先级为“”最高“-a”次之“-o”最低。
同时bash也能完成简单的算术运算格式如下
$[expression]
例如var1=2
var2=$[var1*10+1]
则var2的值为21。
2.if条件语句
shell程序中的条件分支是通过if条件语句来实现的其一般格式为
if 条件命令串
then
条件为真时的命令串
else
条件为假时的命令串
fi
3.for 循环
for循环对一个变量的可能的值都执行一个命令序列。赋给变量的几个数值既可以在程序内
以数值列表的形式提供也可以在程序以外以位置参数的形式提供。for循环的一般格式为
for 变量名 [in 数值列表]
do
若干个命令行
done
变量名可以是用户选择的任何字符串如果变量名是var则在in之后给出的数值将顺序
替换循环命令列表中的$var。如果省略了in则变量var的取值将是位置参数。对变量的
每一个可能的赋值都将执行do和done之间的命令列表。
4.while和until 循环
while 和 until命令都是用命令的返回状态值来控制循环的。While 循环的一般格式为
while
若干个命令行1
do
若干个命令行2
done
只要while的“若干个命令行1”中最后一个命令的返回状态为真while循环就继续执行
do...done之间的“若干个命令行2”。
until命令是另一种循环结构它和while命令相似其格式如下
until
若干个命令行1 do
若干个命令行2
done
until循环和while循环的区别在于while循环在条件为真时继续执行循环而until则
是在条件为假时继续执行循环。
Shell还提供了true和false两条命令用于建立无限循环结构的需要它们的返回状态分
别是总为0或总为非0
5.case 条件选择
if条件语句用于在两个选项中选定一项而case条件选择为用户提供了根据字符串或变量
的值从多个选项中选择一项的方法其格式如下
case string in
exp-1)
若干个命令行1
exp-2)
若干个命令行2
??
*)
其他命令行
esac
shell通过计算字符串string的值将其结果依次和表达式exp-1、exp-2等进行比较直
到找到一个匹配的表达式为止如果找到了匹配项则执行它下面的命令直到遇到一对分号
()为止。
在case表达式中也可以使用shell的通配符(“*”、“”、“[ ]”)。通常用“*”作为
case命令的最后表达式以便使在前面找不到任何相应的匹配项时执行“其他命令行”的命
令。
6.无条件控制语句break和continue
break 用于立即终止当前循环的执行而contiune用于不执行循环中后面的语句而立即开
始下一个循环的执行。这两个语句只有放在do和done之间才有效。
7.函数定义
在shell中还可以定义函数。函数实际上也是由若干条shell命令组成的因此它与shell
程序形式上是相似的不同的是它不是一个单独的进程而是shell程序的一部分。函数定
义的基本格式为
functionname
{
若干命令行
}
调用函数的格式为
functionname param1 param2 ??
shell函数可以完成某些例行的工作而且还可以有自己的退出状态因此函数也可以作为
if、while等控制结构的条件。
在函数定义时不用带参数说明但在调用函数时可以带有参数此时shell将把这些参数分
别赋予相应的位置参数$1、$2、...及$*。 8.命令分组
在shell中有两种命令分组的方法“()”和“{}”前者当shell执行()中的命令时将再
创建一个新的子进程然后这个子进程去执行圆括弧中的命令。当用户在执行某个命令时不
想让命令运行时对状态集合(如位置参数、环境变量、当前工作目录等)的改变影响到下面语
句的执行时就应该把这些命令放在圆括弧中这样就能保证所有的改变只对子进程产生影
响而父进程不受任何干扰{}用于将顺序执行的命令的输出结果用于另一个命令的输入(管
道方式)。当我们要真正使用圆括弧和花括弧时(如计算表达式的优先级)则需要在其前面
加上转义符(\)以便让shell知道它们不是用于命令执行的控制所用。
9.信号
trap命令用于在shell程序中捕捉到信号之后可以有三种反应方式
(1)执行一段程序来处理这一信号
(2)接受信号的默认操作
(3)忽视这一信号
trap对上面三种方式提供了三种基本形式
第一种形式的trap命令在shell接收到signal list清单中数值相同的信号时将执行双
引号中的命令串。
trap 'commands' signal-list
trap "commands" signal-list
为了恢复信号的默认操作使用第二种形式的trap命令
trap signal-list
第三种形式的trap命令允许忽视信号
trap " " signal-list
注意
(1)对信号11(段违例)不能捕捉因为shell本身需要捕捉该信号去进行内存的转储。
(2)在trap中可以定义对信号0的处理(实际上没有这个信号)shell程序在其终止(如执
行exit语句)时发出该信号。
(3)在捕捉到signal-list中指定的信号并执行完相应的命令之后如果这些命令没有将sh
ell程序终止的话shell程序将继续执行收到信号时所执行的命令后面的命令这样将很
容易导致shell程序无法终止。
另外在trap语句中单引号和双引号是不同的当shell程序第一次碰到trap语句时
将把commands中的命令扫描一遍。此时若commands是用单引号括起来的话那么shell
不会对commands中的变量和命令进行替换否则commands中的变量和命令将用当时具体的
值来替换。
五、运行shell程序的方法
用户可以用任何编辑程序来编写shell程序。因为shell程序是解释执行的所以不需要编
译装配成目标程序按照shell编程的惯例以bash为例程序的第一行一般为“#/bi
n/bash”其中#表示该行是注释叹号“”告诉shell运行叹号之后的命令并用文件的
其余部分作为输入也就是运行/bin/bash并让/bin/bash去执行shell程序的内容。
执行shell程序的方法有三种
(1)sh shell程序文件名
这种方法的命令格式为
bash shell程序文件名
这实际上是调用一个新的bash命令解释程序而把shell程序文件名作为参数传递给它。
新启动的shell将去读指定的文件执行文件中列出的命令当所有的命令都执行完结束。该方法的优点是可以利用shell调试功能。
(2)sh格式为
bash这种方式就是利用输入重定向使shell命令解释程序的输入取自指定的程序文件。
(3)用chmod命令使shell程序成为可执行的
一个文件能否运行取决于该文件的内容本身可执行且该文件具有执行权。对于shell程序
当用编辑器生成一个文件时系统赋予的许可权限都是644(rw-r-r--)因此当用户需要
运行这个文件时只需要直接键入文件名即可。
在这三种运行shell程序的方法中最好按下面的方式选择当刚建立一个shell程序对
它的正确性还没有把握时应当使用第一种方式进行调试。当一个shell程序已经调试好时
应使用第三种方式把它固定下来以后只要键入相应的文件名即可并可被另一个程序所调
用。
六、bash程序的调试
在编程过程中难免会出错有的时候调试程序比编写程序花费的时间还要多shell程序
同样如此。
shell程序的调试主要是利用bash命令解释程序的选择项。调用bash的形式是
bash -选择项 shell程序文件名
几个常用的选择项是
-e如果一个命令失败就立即退出
-n读入命令但是不执行它们
-u置换时把未设置的变量看作出错
-v当读入shell输入行时把它们显示出来
-x执行命令时把命令和它们的参数显示出来
上面的所有选项也可以在shell程序内部用“set -选择项”的形式引用而“set +选择项”
则将禁止该选择项起作用。如果只想对程序的某一部分使用某些选择项时则可以将该部分
用上面两个语句包围起来。
1.未置变量退出和立即退出
未置变量退出特性允许用户对所有变量进行检查如果引用了一个未赋值的变量就终止she
ll程序的执行。shell通常允许未置变量的使用在这种情况下变量的值为空。如果设置
了未置变量退出选择项则一旦使用了未置变量就显示错误信息并终止程序的运行。未置
变量退出选择项为“-u”。
当shell运行时若遇到不存在或不可执行的命令、重定向失败或命令非正常结束等情况时
如果未经重新定向该出错信息会打印在终端屏幕上而shell程序仍将继续执行。要想在
错误发生时迫使shell程序立即结束可以使用“-e”选项将shell程序的执行立即终止。
2.shell程序的跟踪
调试shell程序的主要方法是利用shell命令解释程序的“-v”或“-x”选项来跟踪程序的
执行。“-v”选择项使shell在执行程序的过程中把它读入的每一个命令行都显示出来
而“-x”选择项使shell在执行程序的过程中把它执行的每一个命令在行首用一个“+”加
上命令名显示出来。并把每一个变量和该变量所取的值也显示出来因此它们的主要区别
在于在执行命令行之前无“-v”则打印出命令行的原始内容而有“-v”则打印出经过替
换后的命令行的内容。
除了使用shell的“-v”和“-x”选择项以外还可以在shell程序内部采取一些辅助调试
的措施。例如可以在shell程序的一些关键地方使用echo命令把必要的信息显示出来它的作用相当于C语言中的printf语句这样就可以知道程序运行到什么地方及程序目前
的状态。
七、bash的内部命令
bash命令解释程序包含了一些内部命令。内部命令在目录列表时是看不见的它们由shel
l本身提供。常用的内部命令有echo、eval、exec、export、readonly、read、shift、w
ait和点(.)。下面简单介绍其命令格式和功能。
1.echo
命令格式echo arg
功能在屏幕上打印出由arg指定的字符串。
2.eval
命令格式eval args
功能当shell程序执行到eval语句时shell读入参数args并将它们组合成一个新的
命令然后执行。
3.exec
命令格式exec 命令 命令参数
功能当shell执行到exec语句时不会去创建新的子进程而是转去执行指定的命令
当指定的命令执行完时该进程也就是最初的shell就终止了所以shell程序中exec
后面的语句将不再被执行。
4.export
命令格式export 变量名 或export 变量名=变量值
功能shell可以用export把它的变量向下带入子shell从而让子进程继承父进程中的环
境变量。但子shell不能用export把它的变量向上带入父shell。
注意不带任何变量名的export语句将显示出当前所有的export变量。
5.readonly
命令格式readonly 变量名
功能将一个用户定义的shell变量标识为不可变的。不带任何参数的readonly命令将显
示出所有只读的shell变量。
6.read
命令格式
read变量名表
功能从标准输入设备读入一行分解成若干字赋值给shell程序内部定义的变量。
7.shift语句
功能shift语句按如下方式重新命名所有的位置参数变量$2成为$1$3成为$2??在
程序中每使用一次shift语句都使所有的位置参数依次向左移动一个位置并使位置参数
“$#”减一直到减到0。
8.wait
功能是shell等待在后台启动的所有子进程结束。Wait的返回值总是真。
9.exit
功能退出shell程序。在exit之后可有选择地指定一个数字作为返回状态。
10.“.”(点)
命令格式. Shell程序文件名
功能使shell读入指定的shell程序文件并依次执行文件中的所有语句。
1. Linux 脚本编写基础 1.1 语法基本介绍
1.1.1 开头
程序必须以下面的行开始必须方在文件的第一行
#!/bin/sh
符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用
/bin/sh来执行程序。
当编辑好脚本时如果要执行该脚本还必须使其可执行。
要使脚本可执行
编译 chmod +x filename 这样才能用./filename 来运行
1.1.2 注释
在进行shell编程时以#开头的句子表示注释直到这一行的结束。我们真诚地建议
您在程序中使用注释。
如果您使用了注释那么即使相当长的时间内没有使用该脚本您也能在很短的时间内
明白该脚本的作用及工作原理。
1.1.3 变量
在其他编程语言中您必须使用变量。在shell编程中所有的变量都由字符串组成并
且您不需要对变量进行声明。要赋值给一个变量您可以这样写
#!/bin/sh
#对变量赋值
a="hello world"
# 现在打印变量a的内容
echo "A is:"
echo $a
有时候变量名很容易与其他文字混淆比如
num=2
echo "this is the $numnd"
这并不会打印出"this is the 2nd"而仅仅打印"this is the "因为shell会去搜索变
量numnd的值但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是
num变量
num=2
echo "this is the ${num}nd"
这将打印 this is the 2nd
1.1.4 环境变量
由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论因为通常情况
下仅仅在登录脚本中使用环境变量。
1.1.5 Shell命令和流程控制
在shell脚本中可以使用三类命令
1)Unix 命令:
虽然在shell脚本中可以使用任意的unix命令但是还是由一些相对更常用的命令。
这些命令通常是用来进行文件和文字操作的。
常用命令语法及功能
echo "some text": 将文字内容打印在屏幕上
ls: 文件列表
wc �Cl filewc -w filewc -c file: 计算文件行数计算文件中的单词数计算文件中的字符数
cp sourcefile destfile: 文件拷贝
mv oldname newname : 重命名文件或移动文件
rm file: 删除文件
grep 'pattern' file: 在文件内搜索字符串比如grep 'searchstring' file.txt
cut -b colnum file: 指定欲显示的文件内容范围并将它们输出到标准输出设备比如
输出每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆
这是两个完全不同的命令
cat file.txt: 输出文件内容到标准输出设备屏幕上
file somefile: 得到文件类型
read var: 提示用户输入并将输入赋值给变量
sort file.txt: 对file.txt文件中的行进行排序
uniq: 删除文本文件中出现的行列比如 sort file.txt | uniq
expr: 进行数学运算Example: add 2 and 3expr 2 "+" 3
find: 搜索文件比如根据文件名搜索find . -name filename -print
tee: 将数据输出到标准输出设备(屏幕) 和文件比如somecommand | tee outfile
basename file: 返回不包含路径的文件名比如 basename /bin/tux将返回 tux
dirname file: 返回文件所在路径比如dirname /bin/tux将返回 /bin
head file: 打印文本文件开头几行
tail file : 打印文本文件末尾几行
sed: Sed是一个基本的查找替换程序。可以从标准输入比如命令管道读入文本
并将
结果输出到标准输出屏幕。该命令采用正则表达式见参考进行搜索。不要和shell
中的通配符相混淆。比如将linuxfocus 替换为LinuxFocus cat text.file | sed
's/linuxfocus/LinuxFocus/' > newtext.file awk: awk 用来从文本文件中提取字段。
缺省地字段分割符是空格可以使用-F指定其他分割符。
cat file.txt | awk -F, '{print $1 "," $3 }'这里我们使用作为字段分割符同时打
印第一个和第三个字段。如果该文件内容如下 Adam Bor, 34, IndiaKerry Miller, 22, USA
命令输出结果为Adam Bor, IndiaKerry Miller, USA
2) 概念: 管道, 重定向和 backtick
这些不是系统命令但是他们真的很重要。
管道 (|) 将一个命令的输出作为另外一个命令的输入。
grep "hello" file.txt | wc -l
在file.txt中搜索包含有”hello”的行并计算其行数。
在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。
重定向将命令的结果输出到文件而不是标准输出屏幕。
> 写入文件并覆盖旧文件
>> 加到文件的尾部保留旧文件内容。
反短斜线
使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。
命令
find . -mtime -1 -type f -print
用来查找过去24小时-mtime �C2则表示过去48小时内修改过的文件。如果您想
将所有查找到的文件打一个包则可以使用以下脚本 #!/bin/sh
# The ticks are backticks (`) not normal quotes ('):
tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print`
3) 流程控制
1.if
"if" 表达式 如果条件为真则执行then后面的部分
if ....; then
....
elif ....; then
....
else
....
fi
大多数情况下可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否
存在及是否可读等等?
通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。
[ -f "somefile" ] 判断是否是一个文件
[ -x "/bin/ls" ] 判断/bin/ls是否存在并有可执行权限
[ -n "$var" ] 判断$var变量是否有值
[ "$a" = "$b" ] 判断$a和$b是否相等
执行man test可以查看所有测试表达式可以比较和判断的类型。
直接执行以下脚本
#!/bin/sh
if [ "$SHELL" = "/bin/bash" ]; then
echo "your login shell is the bash (bourne again shell)"
else
echo "your login shell is not bash but $SHELL"
fi
变量$SHELL包含了登录shell的名称我们和/bin/bash进行了比较。
快捷操作符
熟悉C语言的朋友可能会很喜欢下面的表达式
[ -f "/etc/shadow" ] && echo "This computer uses shadow passwors"
这里 && 就是一个快捷操作符如果左边的表达式为真则执行右边的语句。
您也可以认为是逻辑运算中的与操作。上例中表示如果/etc/shadow文件存在则打印” This
computer uses shadow passwors”。同样或操作(||)在shell编程中也是可用的。这里有
个例子
#!/bin/sh
mailfolder=/var/spool/mail/james
[ -r "$mailfolder" ]' '{ echo "Can not read $mailfolder" ; exit 1; }
echo "$mailfolder has mail from:"
grep "^From " $mailfolder
该脚本首先判断mailfolder是否可读。如果可读则打印该文件中的"From" 一行。如果不可
读则或操作生效打印错误信息后脚本退出。这里有个问题那就是我们必须有两个命令
-打印错误信息 -退出程序
我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。一般函数将
在下文提及。
不用与和或操作符我们也可以用if表达式作任何事情但是使用与或操作符会更便
利很多。
2.case
case :表达式可以用来匹配一个给定的字符串而不是数字。
case ... in
...) do something here ;;
esac
让我们看一个例子。 file命令可以辨别出一个给定文件的文件类型比如
file lf.gz
这将返回
lf.gz: gzip compressed data, deflated, original filename,
last modified: Mon Aug 27 23:09:18 2001, os: Unix
我们利用这一点写了一个叫做smartzip的脚本该脚本可以自动解压bzip2, gzip 和zip
类型的压缩文件
#!/bin/sh
ftype=`file "$1"`
case "$ftype" in
"$1: Zip archive"*)
unzip "$1" ;;
"$1: gzip compressed"*)
gunzip "$1" ;;
"$1: bzip2 compressed"*)
bunzip2 "$1" ;;
*) echo "File $1 can not be uncompressed with smartzip";;
esac
您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一
个参数值。
也就是说当我们运行
smartzip articles.zip
$1 就是字符串 articles.zip
3. selsect
select 表达式是一种bash的扩展应用尤其擅长于交互式使用。用户可以从一组不同的值
中进行选择。
select var in ... ; do
break
done
.... now $var can be used ....
下面是一个例子
#!/bin/sh
echo "What is your favourite OS?"
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do break
done
echo "You have selected $var"
下面是该脚本运行的结果
What is your favourite OS?
1) Linux
2) Gnu Hurd
3) Free BSD
4) Other
#? 1
You have selected Linux
4.loop
loop表达式
while ...; do
....
done
while-loop 将运行直到表达式测试为真。will run while the expression that we test
for is true.
关键字"break" 用来跳出循环。而关键字”continue”用来不执行余下的部分而直接跳到下
一个循环。
for-loop表达式查看一个字符串列表 (字符串用空格分隔) 然后将其赋给一个变量
for var in ....; do
....
done
在下面的例子中将分别打印ABC到屏幕上
#!/bin/sh
for var in A B C ; do
echo "var is $var"
done
下面是一个更为有用的脚本showrpm其功能是打印一些RPM包的统计信息
#!/bin/sh
# list a content summary of a number of RPM packages
# USAGE: showrpm rpmfile1 rpmfile2 ...
# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm
for rpmpackage in $*; do
if [ -r "$rpmpackage" ];then
echo "=============== $rpmpackage =============="
rpm -qi -p $rpmpackage
else
echo "ERROR: cannot read file $rpmpackage"
fi
done
这里出现了第二个特殊的变量$*该变量包含了所有输入的命令行参数值。 如果您运行showrpm openssh.rpm w3m.rpm webgrep.rpm
此时 $* 包含了 3 个字符串即openssh.rpm, w3m.rpm and webgrep.rpm.
5. 引号
在向程序传递任何参数之前程序会扩展通配符和变量。这里所谓扩展的意思是程序会把通
配符比如*替换成合适的文件名它变量替换成变量值。为了防止程序作这种替换您
可以使用引号让我们来看一个例子假设在当前目录下有一些文件两个jpg文件
mail.jpg 和tux.jpg。
1.2 编译SHELL脚本
#ch#!/bin/sh mod +x filename
cho *.jpg ∪缓螅梢酝ü淙耄?./filename 来执行您的脚本。
这将打印出"mail.jpg tux.jpg"的结果。
引号 (单引号和双引号) 将防止这种通配符扩展
#!/bin/sh
echo "*.jpg"
echo '*.jpg'
这将打印"*.jpg" 两次。
单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量
扩展。
#!/bin/sh
echo $SHELL
echo "$SHELL"
echo '$SHELL'
运行结果为
/bin/bash
/bin/bash
$SHELL
最后还有一种防止这种扩展的方法那就是使用转义字符――反斜杆
echo *.jpg
echo $SHELL
这将输出
*.jpg
$SHELL
6. Here documents
当要将几行文字传递给一个命令时here documents译者注目前还没有见到过对该词适
合的翻译一种不错的方法。对每个脚本写一段帮助性的文字是很有用的此时如果我们四
有那个 here documents就不必用echo函数一行行输出。 一个 "Here document" 以 << 开
头后面接上一个字符串这个字符串还必须出现在here document的末尾。下面是一个例
子在该例子中我们对多个文件进行重命名并且使用here documents打印帮助
#!/bin/sh
# we have less than 3 arguments. Print the help text:
if [ $# -lt 3 ] ; then
cat <
ren -- renames a number of files using sed regular expressions
USAGE: ren 'regexp' 'replacement' files... EXAMPLE: rename all *.HTM files in *.html:
ren 'HTM$' 'html' *.HTM
HELP
exit 0
fi
OLD="$1"
NEW="$2"
# The shift command removes one argument from the list of
# command line arguments.
shift
shift
# $* contains now all the files:
for file in $*; do
if [ -f "$file" ] ; then
newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"`
if [ -f "$newfile" ]; then
echo "ERROR: $newfile exists already"
else
echo "renaming $file to $newfile ..."
mv "$file" "$newfile"
fi
fi
done
这是一个复杂一些的例子。让我们详细讨论一下。第一个if表达式判断输入命令行参数
是否小于3个 (特殊变量$# 表示包含参数的个数) 。如果输入参数小于3个则将帮助文
字传递给cat命令然后由cat命令将其打印在屏幕上。打印帮助文字后程序退出。如果输
入参数等于或大于3个我们就将第一个参数赋值给变量OLD第二个参数赋值给变量NEW。
下一步我们使用shift命令将第一个和第二个参数从参数列表中删除这样原来的第三个
参数就成为参数列表$*的第一个参数。然后我们开始循环命令行参数列表被一个接一个地
被赋值给变量$file。接着我们判断该文件是否存在如果存在则通过sed命令搜索和替换
来产生新的文件名。然后将反短斜线内命令结果赋值给newfile。这样我们就达到了我们的
目的得到了旧文件名和新文件名。然后使用mv命令进行重命名。
4)函数
如果您写了一些稍微复杂一些的程序您就会发现在程序中可能在几个地方使用了相同的代
码并且您也会发现如果我们使用了函数会方便很多。一个函数是这个样子的
functionname()
{
# inside the body $1 is the first argument given to the function
# $2 the second ...
body
}
您需要在每个程序的开始对函数进行声明。
下面是一个叫做xtitlebar的脚本使用这个脚本您可以改变终端窗口的名称。
这里使用了一个叫做help的函数。正如您可以看到的那样这个定义的函数被使用了两次。 #!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
cat <
xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole
USAGE: xtitlebar [-h] "string_for_titelbar"
OPTIONS: -h help text
EXAMPLE: xtitlebar "cvs"
HELP
exit 0
}
# in case of error or if -h is given we call the function help:
[ -z "$1" ] && help
[ "$1" = "-h" ] && help
# send the escape sequence to change the xterm titelbar:
echo -e "33]0;$107"
#
在脚本中提供帮助是一种很好的编程习惯这样方便其他用户和您使用和理解脚本。
命令行参数
我们已经见过$* 和 $1, $2 ... $9 等特殊变量这些特殊变量包含了用户从命令行输
入的参数。迄今为止我们仅仅了解了一些简单的命令行语法比如一些强制性的参数和查
看帮助的-h选项。但是在编写更复杂的程序时您可能会发现您需要更多的自定义的选项。
通常的惯例是在所有可选的参数之前加一个减号后面再加上参数值 (比如文件名)。
有好多方法可以实现对输入参数的分析但是下面的使用case表达式的例子无遗是一个不
错的方法。
#!/bin/sh
help()
{
cat <
This is a generic command line parser demo.
USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2
HELP
exit 0
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;; # function help is called
-f) opt_f=1;shift 1;; # variable opt_f is set
-l) opt_l=$2;shift 2;; # -l takes an argument -> shift by 2
--) shift;break;; # end of options
-*) echo "error: no such option $1. -h for help";exit 1;;
*) break;;
esac done
echo "opt_f is $opt_f"
echo "opt_l is $opt_l"
echo "first arg is $1"
echo "2nd arg is $2"
您可以这样运行该脚本
cmdparser -l hello -f -- -somefile1 somefile2
返回的结果是
opt_f is 1
opt_l is hello
first arg is -somefile1
2nd arg is somefile2
这个脚本是如何工作的呢脚本首先在所有输入命令行参数中进行循环将输入参数与
case表达式进行比较如果匹配则设置一个变量并且移除该参数。根据unix系统的惯例
首先输入的应该是包含减号的参数.
第2部分 实例
现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并
且写一个伪脚本framework.sh该脚本包含了大多数脚本都需要的框架结构是一个非
常不错的主意。这时候在写一个新的脚本时我们只需要执行一下copy命令
cp framework.sh myscript
然后再插入自己的函数。
让我们再看两个例子
二进制到十进制的转换
脚本 b2d 将二进制数 (比如 1101) 转换为相应的十进制数。这也是一个用expr命令
进行数学运算的例子
#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
cat <
b2h -- convert binary to decimal
USAGE: b2h [-h] binarynum
OPTIONS: -h help text
EXAMPLE: b2h 111010
will return 58
HELP
exit 0
}
error()
{
# print an error and exit
echo "$1"
exit 1
} lastchar()
{
# return the last character of a string in $rval
if [ -z "$1" ]; then
# empty string
rval=""
return
fi
# wc puts some space behind the output this is why we need sed:
numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
# now cut out the last char
rval=`echo -n "$1" | cut -b $numofchar`
}
chop()
{
# remove the last character in string and return it in $rval
if [ -z "$1" ]; then
# empty string
rval=""
return
fi
# wc puts some space behind the output this is why we need sed:
numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
if [ "$numofchar" = "1" ]; then
# only one char in string
rval=""
return
fi
numofcharminus1=`expr $numofchar "-" 1`
# now cut all but the last char:
rval=`echo -n "$1" | cut -b 0-${numofcharminus1}`
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;; # function help is called
--) shift;break;; # end of options
-*) error "error: no such option $1. -h for help";;
*) break;;
esac
done
# The main program
sum=0
weight=1
# one arg must be given: [ -z "$1" ] && help
binnum="$1"
binnumorig="$1"
while [ -n "$binnum" ]; do
lastchar "$binnum"
if [ "$rval" = "1" ]; then
sum=`expr "$weight" "+" "$sum"`
fi
# remove the last position in $binnum
chop "$binnum"
binnum="$rval"
weight=`expr "$weight" "*" 2`
done
echo "binary $binnumorig is decimal $sum"
该脚本使用的算法是利用十进制和二进制数权值 (1,2,4,8,16,..)比如二进制"10"可以
这样转换成十进制
0 * 1 + 1 * 2 = 2
为了得到单个的二进制数我们是用了lastchar 函数。该函数使用wc �Cc计算字符个
数然后使用cut命令取出末尾一个字符。Chop函数的功能则是移除最后一个字符。
文件循环程序
或许您是想将所有发出的邮件保存到一个文件中的人们中的一员但是在过了几个月以
后这个文件可能会变得很大以至于使对该文件的访问速度变慢。下面的脚本rotatefile
可以解决这个问题。这个脚本可以重命名邮件保存文件假设为outmail为outmail.1
而对于outmail.1就变成了outmail.2 等等等等...
#!/bin/sh
# vim: set sw=4 ts=4 et:
ver="0.1"
help()
{
cat <
rotatefile -- rotate the file name
USAGE: rotatefile [-h] filename
OPTIONS: -h help text
EXAMPLE: rotatefile out
This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1
and create an empty out-file
The max number is 10
version $ver
HELP
exit 0
}
error()
{
echo "$1" exit 1
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;;
--) break;;
-*) echo "error: no such option $1. -h for help";exit 1;;
*) break;;
esac
done
# input check:
if [ -z "$1" ] ; then
error "ERROR: you must specify a file, use -h for help"
fi
filen="$1"
# rename any .1 , .2 etc file:
for n in 9 8 7 6 5 4 3 2 1; do
if [ -f "$filen.$n" ]; then
p=`expr $n + 1`
echo "mv $filen.$n $filen.$p"
mv $filen.$n $filen.$p
fi
done
# rename the original file:
if [ -f "$filen" ]; then
echo "mv $filen $filen.1"
mv $filen $filen.1
fi
echo touch $filen
touch $filen
这个脚本是如何工作的呢在检测用户提供了一个文件名以后我们进行一个9到1
的循环。文件9被命名为10文件8重命名为9等等。循环完成之后我们将原始文件命
名为文件1同时建立一个与原始文件同名的空文件。
调试
最简单的调试命令当然是使用echo命令。您可以使用echo在任何怀疑出错的地方打印
任何变量值。这也是绝大多数的shell程序员要花费80%的时间来调试程序的原因。Shell
程序的好处在于不需要重新编译插入一个echo命令也不需要多少时间。
shell也有一个真实的调试模式。如果在脚本"strangescript" 中有错误您可以这样
来进行调试
sh -x strangescript
这将执行该脚本并显示所有变量的值。
shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用
sh -n your_script
这将返回所有语法错误