shell脚本初学.docx
《shell脚本初学.docx》由会员分享,可在线阅读,更多相关《shell脚本初学.docx(24页珍藏版)》请在冰豆网上搜索。
shell脚本初学
shell脚本初学
2010-06-0214:
04:
30|分类:
Linux|标签:
|字号大中小订阅
#!
用来指定解释脚本的程序,即由哪个shell解释脚本,也可以是shell外的其他程序。
1)如果外壳脚本的第一个非空白字符不是“#”,则它会使用Bourne外壳。
2)如果外壳脚本的第一个非空白字符是“#”,但不以“#!
”开头时,则它会使用C外壳。
3)如果外壳脚本以“#!
”开头,则“#!
”后面所跟的字符串就是所使用的外壳的绝对路径名。
Bourne外壳的路径名称为/bin/sh,而C外壳则为/bin/csh。
echo
功能是在显示器上显示一段文字,一般起到一个提示的作用,在shell编程中极为常用
该命令的一般格式为:
echo[-n]字符串
其中选项n表示输出文字后不换行;字符串能加引号,也能不加引号。
用echo命令输出加引号的字符串时,将字符串原样输出;用echo命令输出不加引号的字符串时,将字符串中的各个单词作为字符串输出,各字符串之间用一个空格分割。
infoecho和manecho得到详细信息
eval
eval命令处理命令行,先执行所有的shell替换,然后执行命令行。
换言之,
eval语句通知shell接受eval参数,并再次通过命令行处理的所有步骤运行它们。
范例:
1$setabcd
2$echoThelastargumentis\$$#
3Thelastargumentis$4
4$evalechoThelastargumentis\$$#
Thelastargumentisd
5$set-x
$evalechoThelastargumentis\$$#
+evalechothelastargumentis'$4'
++echothelastargumentisd
Thelastargumentisd
说明
1.设置4个位置参量。
2.用户希望得到的结果是显示最后一个位置参量的值。
\$打印出一个美元符。
$#的值是4,即位置参量的个数。
shell求出$#的值后,不会再次解析命令行以得出$4的值。
3.显示的结果是$4,而不是最后那个参数。
4.shell执行完所有的变量替换后,eval命令又执行一次变量替换,然后执行echo命令。
5.打开反显选项来观察解析的顺序。
test或[]
命令DE>testDE>或DE>[DE>可以测试一个条件是否成立,如果测试结果为真,则该命令的ExitStatus为0,如果测试结果为假,则命令的ExitStatus为1(注意与C语言的逻辑表示正好相反)。
这个命令(测试情况为真,true)最基本的形式如下所示:
testoption_flagitem_to_test
如果测试成功,返回值就是0(真,true)。
反之就是1(假,false)。
用户可以任意对这两
个返回值进行测试。
如果打算进行情况为假(false)的测试,请使用下面的格式:
test!
option_flagitem_to_test
常用的位置参数和特殊变量
DE>$0DE>
相当于C语言DE>mainDE>函数的DE>argv[0]DE>
DE>$1DE>、DE>$2DE>...
这些称为位置参数(PositionalParameter),相当于C语言DE>mainDE>函数的DE>argv[1]DE>、DE>argv[2]DE>...
DE>$#DE>
相当于C语言DE>mainDE>函数的DE>argc-1DE>,注意这里的DE>#DE>后面不表示注释
DE>$@DE>
表示参数列表DE>"$1""$2"...DE>,例如可以用在DE>forDE>循环中的DE>inDE>后面。
DE>$?
DE>
上一条命令的ExitStatus
DE>$$DE>
当前Shell的进程号
函数
和C语言类似,Shell中也有函数的概念,但是函数定义中没有返回值也没有参数列表。
例如:
#!
/bin/shfoo(){echo"Functionfooiscalled";}echo"-=start=-"fooecho"-=end=-"
注意函数体的左花括号{和后面的命令之间必须有空格或换行,如果将最后一条命令和右花括号DE>}DE>写在同一行,命令末尾必须有;号。
在定义DE>foo()DE>函数时并不执行函数体中的命令,就像定义变量一样,只是给DE>fooDE>这个名字一个定义,到后面调用DE>fooDE>函数的时候(注意Shell中的函数调用不写括号)才执行函数体中的命令。
Shell脚本中的函数必须先定义后调用,一般把函数定义都写在脚本的前面,把函数调用和其它命令写在脚本的最后(类似C语言中的DE>mainDE>函数,这才是整个脚本实际开始执行命令的地方)。
if/then/elif/else/fi
case/esac
for/do/done
while/do/done
Shell脚本的调试方法
Shell提供了一些用于调试脚本的选项,如下所示:
-n
读一遍脚本中的命令但不执行,用于检查脚本中的语法错误
-v
一边执行脚本,一边将执行过的脚本命令打印到标准错误输出
-x
提供跟踪执行信息,将执行的每一条命令和结果依次打印出来
使用这些选项有三种方法,一是在命令行提供参数
$sh-x./script.sh
二是在脚本开头提供参数
#!
/bin/sh-x
第三种方法是在脚本中用set命令启用或禁用参数
#!
/bin/shif[-z"$1"];thenset-xecho"ERROR:
InsufficientArgs."exit1set+xfi
DE>set-xDE>和DE>set+xDE>分别表示启用和禁用DE>-xDE>参数,这样可以只对脚本中的某一段进行跟踪调试。
#!
/bin/sh
符号#!
用来告诉系统执行该脚本的程序,本例使用/bin/sh。
有许多变量是系统自动设定的,在后面用到这些变量时我们再作说明。
如果你需要处理数学表达式,那么得借助诸如expr的程序。
除了仅在脚本内有效的普通shell变量外,还有环境变量,即那些由export关键字处理过的变量。
本文不讨论环境变量,因为一般只在登录脚本中使用环境变量。
Shell命令和流程控制
在shell脚本中可以使用三类命令:
1)Unix命令:
在shell脚本中可以使用任意unix命令,不过实际上最为常用的通常是那些文件和文字操作相关的命令。
下面介绍一些常用命令语法及功能:
echo"sometext":
在屏幕上输出信息
ls:
文件列表
wc–lfilewc-wfilewc-cfile:
分别计算文件的行数(line)、单词数(word)和字符数(character)
cpsourcefiledestfile:
文件拷贝
mvoldnamenewname:
重命名文件或移动文件
rmfile:
删除文件
grep'pattern'file:
在文件内搜索字符串或和正则表达式匹配的字符串
cut-bcolumnfile:
将指定范围内的文件内容输出到标准输出设备(屏幕)上。
比如:
输出每行第5至9个字符cut-b5-9file.txt,注意不要和cat命令混淆,这是两个完全不同的命令
catfile.txt:
输出文件内容到标准输出设备(屏幕)上
filesomefile:
取得文件somefile的文件类型
readvar:
提示用户输入,并将输入内容赋值给变量var
sortfile.txt:
对file.txt文件所有行进行排序
uniq:
只输出文件中内容不一致的行,如:
sortfile.txt|uniq
expr:
进行数学运算,如要进行2+3的运算,命令为:
expr2"+"3
find:
搜索文件,如根据文件名搜索:
find.-namefilename-print
tee:
将数据输出到标准输出设备(屏幕)和文件,比如:
somecommand|teeoutfile
basenamefile:
返回不包含路径的文件名,如:
basename/bin/tux会返回tux
dirnamefile:
返回文件所在路径,如:
dirname/bin/tux会返回/bin
headfile:
打印文本文件开头几行
tailfile:
打印文本文件末尾几行
sed:
Sed是一个基本的查找替换程序。
可以从标准输入(如命令管道)读入文本,并将结果输出到标准输出(屏幕);该命令采用正则表达式进行搜索。
不要和shell中的通配符相混淆。
比如将ubuntu替换为Ubuntu:
cattext.file|sed's/ubuntu/Ubuntu/'>newtext.file
awk:
awk用来提取文本文件中的字段。
缺省的字段分割符是空格,可以使用-F指定其它分割符。
catfile.txt|awk-F,'{print$1","$3}',这里我们使用,作为字段分割符,同时打印第一和第三个字段。
如果该文件内容为AdamBor,34,IndiaKerryMiller,22,USA,则上述命令的输出为:
AdamBor,IndiaKerryMiller,USA
2)概念:
管道,重定向和backtick
尽管这些都不是系统命令,不过它们扮演着相当重要的角色。
管道(|)将一个命令的输出作为另外一个命令的输入。
代码:
grep:
hello"file.txt|wc-l
上述命令会在file.txt中搜索包含有”hello”的行并计算行数,这里grep命令的输出成了wc命令的输入。
当然您可以使用多个命令。
重定向:
将命令的结果输出到文件,而不是标准输出(屏幕)。
>写入文件并覆盖旧文件
>>加到文件的尾部,保留旧文件内容。
反短斜线
使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。
find.-mtime-1-typef-print
上述命令可以查找过去24小时(-mtime–2则表示过去48小时)内修改过的文件。
如果你想将所有查找到的文件打一个包,则可以使用以下脚本:
#!
/bin/sh
#Theticksarebackticks(`)notnormalquotes('):
tar-zcvflastmod.tar.gz`find.-mtime-1-typef-print`
3)流程控制
"if"表达式如果条件为真则执行then后的部分:
if.....;then
.....
elif.....;then
......
else
......
fi
大多数情况下,可以使用测试命令来对条件进行测试。
比如可以比较字符串、判断文件是否存在及是否可读等等…通常用"[]"来表示条件测试,注意这里的空格很重要,要确保方括号前后的空格。
[-f"somefile"]:
判断是否是一个文件
[-x"/bin/ls"]:
判断/bin/ls是否存在并有可执行权限
[-n"$var"]:
判断$var变量是否有值
["$a"="$b"]:
判断$a和$b是否相等
执行mantest可以查看所有测试表达式可以比较和判断的类型。
直接执行以下脚本:
#!
/bin/sh
if["$SHELL"="/bin/bash"];then
echo"yourloginshellisthebash(bourneagainshell)"
else
echo"yourloginshellisnotbashbut$SHELL"
fi
变量$SHELL包含了登录shell的名称,我们拿它和/bin/bash进行比较。
快捷操作符
熟悉C语言的朋友可能会很喜欢下面的表达式:
[-f"/etc/shadow"]&&echo"Thiscomputerusesshadowpasswors"
这里的&&就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。
你也可以把它看作逻辑运算的与操作。
上述脚本表示如果/etc/shadow文件存在,则打印”Thiscomputerusesshadowpasswors”。
同样或操作(||)在shell编程中也可以用,例如:
#!
/bin/sh
mailfolder=/var/spool/mail/james
[-r"$mailfolder"]||{echo"Cannotread$mailfolder";exit1;}
echo"$mailfolderhasmailfrom:
"
grep"^From"$mailfolder
该脚本首先判断mailfolder是否可读,如果可读则打印该文件中的"From"一行。
如果不可读则或操作生效,打印错误信息后脚本退出。
这里有个问题,那就是我们必须有两个命令:
-打印错误信息
-退出程序
我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。
一般函数将在下文提及。
不用与和或操作符,我们也可以用if表达式作任何事情,但是使用与或操作符会更便利很多。
case表达式可以用来匹配一个给定的字符串,而不是数字。
case...in
...)dosomethinghere;;
esac
让我们看一个例子,file命令可以辨别出一个给定文件的文件类型,如:
filelf.gz,该命令输出结果为:
引用:
lf.gz:
gzipcompresseddata,deflated,originalfilename,
lastmodified:
MonAug2723:
09:
182001,os:
Unix
我们利用这一点写了一个叫做smartzip的脚本,该脚本可以自动解压bzip2,gzip和zip类型的压缩文件:
#!
/bin/sh
ftype=`file"$1"`
case"$ftype"in
"$1:
Ziparchive"*)
unzip"$1";;
"$1:
gzipcompressed"*)
gunzip"$1";;
"$1:
bzip2compressed"*)
bunzip2"$1";;
*)error"File$1cannotbeuncompressedwithsmartzip";;
esac
您可能注意到我们在这里使用了一个特殊的变量$1。
该变量包含了传递给该程序的第一个参数值。
也就是说,当我们运行:
smartziparticles.zip
$1就是字符串articles.zip
select表达式是一种bash的扩展应用,尤其擅长于交互式使用。
用户可以从一组不同的值中进行选择。
selectvarin...;do
break
done
....now$varcanbeused....
下面是一个例子:
#!
/bin/sh
echo"WhatisyourfavouriteOS?
"
selectvarin"Linux""GnuHurd""FreeBSD""Other";do
break
done
echo"Youhaveselected$var"
下面是该脚本运行的结果:
WhatisyourfavouriteOS?
1)Linux
2)GnuHurd
3)FreeBSD
4)Other
#?
1
YouhaveselectedLinux
您也可以在shell中使用如下的loop表达式:
while...;do
....
done
while-loop将运行直到表达式测试为真。
willrunwhiletheexpressionthatwetestforistrue.关键字"break"用来跳出循环。
而关键字”continue”用来不执行余下的部分而直接跳到下一个循环。
for-loop表达式查看一个字符串列表(字符串用空格分隔)然后将其赋给一个变量:
forvarin....;do
....
done
在下面的例子中,将分别打印ABC到屏幕上:
#!
/bin/sh
forvarinABC;do
echo"varis$var"
done
下面是一个更为有用的脚本showrpm,其功能是打印一些RPM包的统计信息:
#!
/bin/sh
#listacontentsummaryofanumberofRPMpackages
#USAGE:
showrpmrpmfile1rpmfile2...
#EXAMPLE:
showrpm/cdrom/RedHat/RPMS/*.rpm
forrpmpackagein$*;do
if[-r"$rpmpackage"];then
echo"===============$rpmpackage=============="
rpm-qi-p$rpmpackage
else
echo"ERROR:
cannotreadfile$rpmpackage"
fi
done
这里出现了第二个特殊的变量$*,该变量包含了所有输入的命令行参数值。
如果您运行showrpmopenssh.rpmw3m.rpmwebgrep.rpm
此时$*包含了3个字符串,即openssh.rpm,w3m.rpmandwebgrep.rpm.
引号
在向程序传递任何参数之前,程序会扩展通配符和变量。
这里所谓扩展的意思是程序会把通配符(比如*)替换成合适的文件名,它变量替换成变量值。
为了防止程序作这种替换,您可以使用引号:
让我们来看一个例子,假设在当前目录下有一些文件,两个jpg文件,mail.jpg和tux.jpg。
#!
/bin/sh
echo*.jpg
这将打印出"mail.jpgtux.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
Heredocuments
当要将几行文字传递给一个命令时,heredocuments(译者注:
目前还没有见到过对该词适合的翻译)一种不错的方法。
对每个脚本写一段帮助性的文字是很有用的,此时如果我们四有那个heredocuments就不必用echo函数一行行输出。
一个"Heredocument"以<<开头,后面接上一个字符串,这个字符串还必须出现在heredocument的末尾。
下面是一个例子,在该例子中,我们对多个文件进行重命名,并且使用heredocuments打印帮助:
#!
/bin/sh
#wehavelessthan3arguments.Printthehelptext:
if[$#-lt3];then
catUSAGE:
ren'regexp''replacement'files...
EXAMPLE:
renameall*.HTMfilesin*.html:
ren'HTM$''html'*.HTM
HELP
exit0
fi
OLD="$1"
NEW="$2"
#Theshiftcommandremovesoneargumentfromthelistof
#commandlinearguments.
shift
shift
#$*containsnowallthefiles:
forfilein$*;do
if[-f"$file"];then
newfile=`echo"$file"|sed"s/${OLD}/${NEW}/g"`
if[-f"$newfile"];then
echo"ERROR:
$newfileexistsalready"
else
echo"renaming$fileto$newfile..."
mv"$file""$newfile"
fi
fi
done
这是一个复杂一些的例子。
让我们详细讨论一下。
第一个if表达式判断输入命令行参数是否小于3个(特殊变量$#表示包含参数的个数)。
如果输入参数小于3个,则将帮助文字传递给cat命令,然后由cat命令将其打印在屏幕上。
打印帮助文字后程序退出。
如果输入参数等于或大于3个,我们就将第一个参数赋值给变量OLD,第二个参数赋值给变量NEW。
下一步,我们使用shift命令将第一个和第二个参数从参数列表中删除,这样原来的第三个参数就成为参数列表$*的第一个参数。
然后我们开始循环,命令行参数列表被一个接一个地被赋值给变量$file。
接着我们判断该文件是否存在,如果存在则通过sed命令搜索和替换来产生新的文件名。
然后将反短斜线内