linux shell 脚本入门
参考文章位置:http://learn.akae.cn/media/ch31s05.html
1.Linux 脚本编写基础...1
1.1 语法基本介绍...1
1.1.1 开头...1
1.1.2 注释...2
1.1.3 变量...2
1.1.4 环境变量...2
1.1.5 Shell命令和流程控制...2
2.case.5
3. selsect6
4.loop.6
5. 引号...7
6. Here documents8
7.脚本深入...15
1、条件测试操作:...15
1.2、整数值 比较:...16
1.3:字符串比较:...16
1.4:逻辑测试:...17
2:if语句的结构:...18
3、使用for魂环语句...20
4、使用while循环语句...21
附录:...23
a.23
b.23
c. Linux中变量$#,$@,$0,$1,$2,$*,$$,$?的含义...23
程序必须以下面的行开始(必须方在文件的第一行):
#!/bin/sh
符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序。
当编辑好脚本时,如果要执行该脚本,还必须使其可执行。
要使脚本可执行:
编译 chmod +xfilename 这样才能用./filename来运行
在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我们真诚地建议您在程序中使用注释。
如果您使用了注释,那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用
及工作原理。
在其他编程语言中您必须使用变量。在shell编程中,所有的变量都由字符串组成,并且您不需要对变量
进行声明。要赋值给一个变量,您可以这样写:
#!/bin/sh
#对变量赋值:
a="hello world"
# 现在打印变量a的内容:
echo "A is:"
echo $a
有时候变量名很容易与其他文字混淆,比如:
num=2
echo "this is the $numnd"
这并不会打印出"thisis the 2nd",而仅仅打印"thisis the ",因为shell会去搜索变量numnd的值,
但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量:
num=2
echo "this is the ${num}nd"
这将打印: this isthe 2nd
由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录
脚本中使用环境变量。
在shell脚本中可以使用三类命令:
1)Unix 命令:
虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令。这些命令通常是用来
进行文件和文字操作的。
常用命令语法及功能
echo "some text": 将文字内容打印在屏幕上
ls: 文件列表
wc –l 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
命令输出结果为:AdamBor, IndiaKerry Miller, USA
这些不是系统命令,但是他们真的很重要。
管道 (|) 将一个命令的输出作为另外一个命令的输入。
grep "hello" file.txt | wc -l
在file.txt中搜索包含有”hello”的行并计算其行数。
在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。
重定向:将命令的结果输出到文件,而不是标准输出(屏幕)。
> 写入文件并覆盖旧文件
>> 加到文件的尾部,保留旧文件内容。
反短斜线
使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。
命令:
find . -mtime -1 -type f -print
用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。如果您
想将所有查找到的文件打一个包,则可以使用以下脚本:
#!/bin/sh
# The ticks are backticks (`) not normalquotes ('):
tar -zcvf lastmod.tar.gz `find . -mtime -1-type f -print`
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文件存在
则打印” Thiscomputer 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表达式作任何事情,但是使用与或操作符会更便利很多。
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 beuncompressed with smartzip";;
esac
您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一个参数值。
也就是说,当我们运行:
smartzip articles.zip
$1 就是字符串 articles.zip
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" "GnuHurd" "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
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 RPMpackages
# 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
这里出现了第二个特殊的变量$*,该变量包含了所有输入的命令行参数值。
如果您运行showrpmopenssh.rpm w3m.rpm webgrep.rpm
此时 $* 包含了 3 个字符串,即openssh.rpm, w3m.rpm and webgrep.rpm.
在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓扩展的意思是程序会把通配符
(比如*)替换成合适的文件名,它变量替换成变量值。为了防 止程序作这种替换,您可以使用
引号:让我们来看一个例子,假设在当前目录下有一些文件,两个jpg文件, mail.jpg 和tux.jpg。
1.2 编译SHELL脚本
#ch#!/bin/sh mod +x filename
cho *.jpg ∪缓螅梢酝ü 淙耄?./filename 来执行您的脚本。
这将打印出"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
当要将几行文字传递给一个命令时,here documents(译者注:目前还没有见到过对该词适合的翻译)
一种不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果我们四有那个 here documents
就不必用echo函数一行行输出。 一个 "Here document" 以 << 开头,后面接上一个字符串,这个字符串
还必须出现在heredocument的末尾。下面是一个例子,在该例子中,我们对多个文件进行重命名,并且
使用heredocuments打印帮助:
#!/bin/sh
# we have less than 3 arguments. Print thehelp text:
if [ $# -lt 3 ] ; then
cat <
ren -- renames a number of files using sedregular 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 argumentfrom 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 existsalready"
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 argumentgiven 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 wecall the function help:
[ -z "$1" ] && help
[ "$1" = "-h" ]&& help
# send the escape sequence to change thexterm 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 iscalled
-f) opt_f=1;shift 1;; # variable opt_f isset
-l) opt_l=$2;shift 2;; # -l takes anargument -> shift by 2
--) shift;break;; # end of options
-*) echo "error: no such option $1. -hfor 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 -- -somefile1somefile2
返回的结果是:
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 thisis 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 andreturn it in $rval
if [ -z "$1" ]; then
# empty string
rval=""
return
fi
# wc puts some space behind the output thisis 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 -b0-${numofcharminus1}`
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;; # function help iscalled
--) 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 –c计算字符个数,
然后使用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.1to 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. -hfor 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
这将返回所有语法错误。
在Shell脚本中执行使用if语句的好处是:可以根据特定的条件(eg:判断备份目录是否存在)来决定是否执行某项操作,当满足不同的条件时执行不同的操作(eg:备份目录不存在则创建该目录,否则跳过操作)。该文将分别从条件测试操作,if语句结构,应用示例这三个方面讲解if语句在Shell脚本中的应用。
需要在Shell脚本中有选择性地执行任务时,首先面临的问题就是,如何设置命令执行的条件?
在Shell环境中,可以根据命令执行后返回状态值来判断该命令是否成功执行,当返回值为0是表示成功执行,否则(非0值)表示执行失败。用于特定条件表达式的测试时,可以使用Linux系统中提供的专用工具——test命令、
使用test测试命令时,可以有以下两种形式。
test 条件表达式
【条件表达式】
这两种方式的作用完全相同,但通常后一种形式更为常用,也更贴近编程习惯。需要注意的是,方括号“[”或者“]”与条件表达式语句之间至少需要有一个空格进行分隔。
根据需要判断的条件内容不同,条件操作也不同,最常用的条件主要包括文件状态测试,比较整数值大小,比较字符串,以及同时判断多个条件时的逻辑关系,下面将分别进行讲解。以下主要采用方括号的测试形式。
1.1、测试文件状态
文件状态测试是指根据给定的路径名称,判断该名称对应的是文件还是目录,或者判断文件是否可读,可写,可执行等。根据判断的状态不同,在条件表达式中需要使用不同的操作选项。
-d:测试是否为目录(Directory)。
-e:测试目录或文件是否存在(Exist)。
-f:测试是否为文件(File)。
-r:测试当前用户是否有权限读取(Read)。
-w:测试当前用户是否有权限写入(Write)。
-x:测试当前用户是否可执行(Excute)该文件。
-L:测试是否为符号连接(Link)文件。
执行条件测试操作以后,通过预定义变量“$?”可以获得测试命令的返回状态值,从而能够判断该条件是否成立(返回0值表示条件成立,非0值表示条件不成立)。但通过这种方式查看测试结果会比较繁琐。
例1:测试“/etc/hosts”是否是目录,并通过“$?”变量查看返回状态值,据此判断测试结果。
[ -f /etc/hosts ]
echo $?
0 //返回值为0,表示上一步测试的条件成立。
例2:测试“/media/cdrom/Server”及其父目录是否存在,如果存在则显示“YES”否则不输出任何信息。
[ -e /media/cdrom/Server ] && echo"YES"
//无输出表示该目录不存在
[ -e /media/cdrom ] &&echo"YES"
YES
整数值比较是指根据给定的两个整数值,判断第一个数是否大于、等于、小于。。。。。。第2个数,可以使用的操作选项如下:
-eq:第一个数等于(Equal)第二个数。
-ne:第一个数不等于(Not Equal)第二个数。
-gt:第一个数大于(Greater Than)第二个数。
-lt:第一个数小于(Lesser Than)第二个数。
-le:第一个数小于或等于(Lesser or Equal)第二个数。
-ge:第一个数大于或等于(Greater or Equal)第二个数。
整数值比较的测试操作在Shell脚本编写中的应用较多,如:用于判断磁盘使用率、登录用户数量是否超标以及用于控制脚本语句的循环次数等。
例1:测试当前登录到系统中的用户数量是否小于或等于10,是则输出”YES“。
who | wc -l
5
[ `who` | wc -l` -le 10 ] && echo ”YES"
YES
例2:提取出"/boot“分区的磁盘使用率,并判断是否超过95%(为了便于理解,操作步骤适当进行分解,嘿嘿!)
df -hT | grep ”/boot“ | awk '{print $6}'
12%
BootUsage=`df -hT | grep ”/boot" | awk '{print $6}'| cut -d "%" -f 1`
echo $BootUsage
12
[ $BootUsage -gt 95 ] &&echo"YES" //无输出表示未超标
字符串比较可以用于检查用户输入,如:在提供交互式操作时,判断用户输入的选项项是否与指定的变量内容相匹配。“=”、“!=”操作选项分别表示匹配、不匹配。“-z”操作选项用于检查字符串是否为空。其中,“!”符号用于取反,表示相反的意思。
eg:提示用户输入一个文件路径,并判断是否是“/etc/inittab”,如果是则显示“YES”.
read -p "Location: " FilePath
Location:/etc/inittab
[ $FilePath = "/etc/inittab" ] && echo"YES"
YES
eg: 若当前环境变量LANG的内容不是“en.US”,则输出LANG变量的值,否则无输出。
[ $LANG != "en.US" ] && echo $LANG
zh_CN.UTF-8
eg:使用touch命令建立一个新文件,测试其内容是否为空,向文件中写入内容后,再次进行测试。
touch zero.file
[ -z `cat zero.file` ] && echo "yes"
yes
echo "something" > zero.file
[ -z `cat zero.file` ] && echo yes
//无输出
逻辑测试是指同时使用的两个(或多个)条件表达式之间的关系。用户可以同时测试多个条件,根据这些条件是否同时成立或者只要有其中一个条件成立等情况,来决定采取何种操作。逻辑测试可以使用的操作选项如下。
>&&:逻辑与,表示前后两个表达式都成立时整个测试结果才为真,否则结果为假。在使用test命令形式进程测试时,此选项可以改为" -a"。
>||:逻辑或,表示前后两个条件至少有一个成立时整个测试结果即为真,否则结果为假。在使用test命令形式进行测试时,此选项可以改为"-o“。
>!:逻辑否,表示当指定的条件表达式不成立时,整个测试命令的结果为真。
在上述逻辑测试的操作选项中,”&&“和”||“通常也用于间隔不同的命令操作,其作用是相似的。同时使用多个逻辑运算操作时,一般安装从左到右的顺序进行测试。
eg:测试当前的用户是否是teacher,若不是则提示”Not teacher“。
echo $USER
root
[ $USER = “teacher” ] || echo "Not teacher"
Not teacher
eg:只要"/etc/rc.d/rc.local"或者"/etc/init.d/local'中有一个是文件,则显示"YES",否则无任何输出。
[ -f /etc/rc.d/rc.local ] || [ -f /etc/init.d/rc.local ]&& echo "yes“
yes
eg:测试”/etc/profile“文件是否有可执行权限,若确实没有可执行权限,则提示”No x mode.“的信息。
[ ! -x ”/etc/profile" ] && echo "No xmode."
No x mode.
eg:若当前的用户是root且使用的Shell程序是"/bin/bash",则显示"YES“,否则无任何输出。
echo $USER $SHELL
root /bin/bash
[ $USER = ”root" ] && [ $SHELL ="/bin/bash" ] && echo "yes"
yes
前面什么知道了一下条件测试操作,实际上使用"&&“和”||“逻辑测试可以完成简单的判断并执行相应的操作,但是当需要选择执行的命令语句较多时,再使用这种方式将使命令行语句显得很复杂,难以阅读。而使用if语句,则可以更好地体现有现在性执行的程序结构,使得层次分明,清晰易懂。
if语句的选择结构由易到难可以分为三种类型,分别适用于不同的应用场合。
2.1、单分支的if语句。
单分支的if语句是最简单的选择结构,这种结果只判断指定的条件,当”条件成立“时执行相应的操作,否则不做任何操作。单分支使用的语句格式如下。
if 条件测试命令
then
命令序列
fi
在上述语句中,首先通过判断条件测试命令的返回状态值是否为0(条件成立),如果是,则执行then后面的一条或多台可执行语句(命令序列),一直到fi为止表示结束,如果条件测试命令的返回值不为0(条件不成立),则直接去执行fi后面的语句。
2.2、双分支的if语句。
双分支的if语句使用了两路命令操作,在”条件成立‘、“条件不成立时分别执行不同的命令序列”。双分支使用的语句格式如下:
if 条件测试命令
then
命令序列1;
else
命令序列2;
fi
在上述语句中,首先通过if判断条件测试命令的返回状态值是否为0(条件成立),如果是,则执行then后面的一条或多条可执行语句(命令序列1),然后跳转至fi结束判断,如果条件测试命令的返回状态值不为0(条件不成立),则执行else后面的语句,一直到fi表示结束。
2.3、多分支的if语句。
由于if语句可以根据条件测试命令的两种状态分别进行操作,所以能够嵌套使用,进行多次判断(如:首先判断某学生的得分是否及格,如及格则再次判断是否高于90分。。。)多重分支使用的语句格式如下。
if 条件测试命令1
then
命令序列1
elif 条件测试命令2
then
命令序列2
else
命令序列3
fi
上面的语法格式中只嵌套了一个elif语句,实际上if语句中可以嵌套多个elif语句。if语句的嵌套在编写Shell脚本时并不常用,因为多重嵌套容易使程序结构变得复杂。需要使用多重分支程序结构时,更多的是使用case语句来实现。
eg:检查"/var/log/messages'文件是否存在,若存在则统计文件内容的行数并输出,否则不做任何操作。
vi chklog.sh
#!/bin/bash
LogFile="/var/log/messages"
if [ -f $LogFile ] ; then
wc -l $LogFile
fi
shchklog.sh //sh是bash的符号链接
eg:提示用户指定备份目录的路径,若目录已存在则显示提示信息后跳过,否则显示相应提示信息后创建该目录。
[root@localhost ~]# vi mkbak.sh
#!/bin/bash
read -p "What is your directory:"Bakdir
if [ -d $Bakdir ] ; then
echo "$Bakdir already exist."
else
echo "Bakdir is not exist,will makeit."
mkdir $Bakdir
fi
eg:统计当前登录到系统中的用户数量,并判断是否超过三个,若是则显示实际数量并给出警告信息,否则列出登录的用户账号成名及所在终端。
[root@localhost ~]# vim chkuser.sh
#!/bin/bash
UserNum=`wc -l`
if [ $UserNum -gt 3 ] ; then
echo "Alert , too many login users ( Total: $UserNum )."
else
echo "Login users:"
who | awk `{print $1,$2}`
fi
eg:检查portmap进程是否已经存在,若已经存在则输出“portmap service is running”;否则检查是否存在“/etc/rc.d/init.d/portmap”可执行脚本,存在则启动portmap服务,否则提示“noportmap script file.”。
[root@localhost ~]# vim chkportmap.sh
#!/bin/bash
pgrep portmap &> /dev/null
if [ $? -eq 0 ]; then
echo "protmap service is running."
elif
[ -x "/etc/rc.d/init.d/portmap" ]; then
service portmap start
else
echo "no portmap script file."
fi
eg:每隔五分钟监测一次mysqld服务程序的运行状态,若发现mysqld进程已终止,则在“/var/log/messages”文件中追加写入日志信息(包括当时时间),并重启mysqld服务,否则不进程任何操作。
vi chkmysql.sh
#!/bin/bash
service mysqld status &> /dev/null
if [ $? -ne 0 ]; then
echo "At time:`date`:Mysql Server is down." >>/var/log/messages
service mysqld restart
fi
chmod u+x chkmysql.sh
crontab -e
*/5 * * * * /root/chkmysql.sh
多多联系喔!
在Shell脚本中使用for循环语句时,可以为变量设置一个取值列表,每次读取列表中不同的变量值并执行相关命令操作,变量值用完以后则退出循环。Shell中的for语句不需要执行条件判断,其使用变量的取值来自于预先设置的值列表。
for语句结构:
for 变量名 in取值列表
do
命令序列
done
在上述语句中,使用in关键字为用户自定义变量设置了一个取值列表(以空格分隔的多个值),for语句第一次执行时首先将列表中的第一个取值赋给该变量。然后执行do后边的命令序列;然后再将列表中的第二个取值赋给该变量,然后执行do后边的命令序列......如此循环,直到取值列表中的所有值都已经用完,最后将跳至dine语句,表示结束循环。
for语句示例:
eg:依次输出三条文件信息,包括一天中的"Morning"、"Noon"、"Evening"字串。
vi showday.sh
#!/bin/bash
for TM in "Morning" "Noon""Evening"
do
echo "The $TM of the day."
done
eg:对于使用“/bin/bash”登录Shell的系统用户,检查他们在"/opt"目录中拥有的子目录或文件数量,如何超过100个,则列出具体数量及对应的用户账号。
vi chkfileown.sh
#!/bin/bash
DIR="/opt" //设置检查的目标目录
LMT=100 //设置文件数量的限制值
ValidUsers=`grep "/bin/bash" /etc/passwd | cut-d ":" -f1` //找出使用bash的系统用户列表
for UserName in $ValidUsers
do
Num=`find $DIR -user $UserName | wc-l` //统计每个用户拥有的文件数
if [ $Num -gt $LMT ] ; then
echo "$UserName have $Num files."
fi
done
sh chkfileown.sh
root have 20998 files
在Shell脚本中使用while循环语句时,将可以根据特定的条件重复执行一个命令列表,直到该条件不再满足时为至。除非有特别需要,否则在脚本程序中应该是避免出现无限循环执行命令的情况,因为若无法跳出循环的话,后边的明白操作将无法执行。为了控制循环次数,通常会在执行的命令序列中包含修改测试条件的语句,当循环达到一定次数后,测试将不再成立,从而可以结束循环。
while语句的结构
while 条件测试命令
do
命令序列
done
在上述语句中,首先通过while判断条件测试命令的返回状态值是否为0(条件成立),如果是,则执行do后边的命令序列,然后返回到while再次进行条件测试并判断返回状态值,如果条件仍然成立,则继续执行do后边的命令序列,然后返回到while重复条件测试......如此循环,直到所测试的条件不成立时,跳转到done语句,表示结束循环。
使用while循环语句时,有两个特殊的条件测试返回值,即“true”(真)、"false"(假)。使用“true”作为测试条件时,条件将永远成立,循环体内的语句将无限次执行下去,反之使用“false”则条件永远不成立,循环体内的语句将不会被执行,这两个特殊值也可以用在if语句的条件测试中。
while语句应用示例:
while语句可以用于需要重复操作的循环系统管理任务,并能够通过设置循环条件来灵活的实现各种管理任务。
eg:由用户从键盘输入一个大于1的整数(如50),并计算从1到该数之间各整数的和。
[root@localhost ~]# vim sumint.sh
#!/bin/bash
read -p "Input a number (>1):" UP
i=1
Sum=0
while [ $i -le $UP ]
do
Sum=`expr $Sum + $i`
i=`expr $i + 1`
done
echo "The sum of 1-$UP is : $Sum"
[root@localhost ~]# sh sumint.sh
Input a number (>1):50
The sum of 1-50 is : 1275
[root@localhost ~]#
eg:批量添加20个系统用户账号,用户名称依次为“stu1”、"stu2"、“stu3”、.......“stu20”,各用户的初始密码均设置为“123456”。
[root@localhost ~]# vim add20users.sh
#!/bin/bash
i=1
while [ $i -le 20 ]
do
useradd stu$i
echo "123456" | passwd --stdin stu$i &> /dev/null
i=`expr $i + 1`
done
sh add20users.sh
eg:编写一个批量删除用户的脚本程序,将上面添加的20个用户删除。
[root@localhost ~]# vim del20users.sh
#!/bin/bash
i=1
while [ $i -le 20 ]
do
userdel -r stu$i
i=`expr $i + 1`
done
再次查看:cat /etc/passwd就会发现那些用户不再存在。
说了if、for、while、语句后,就可以编写一般的系统管理任务脚本了,记得多多练习!其实除了这些Shell脚本语句外,还有好多好多,如:
case分支语句,until循环、shift移位,以及break和continue循环中断语句。大家可以查询!
1 if [-d $1]
这个的意思是 从外面传一个参数给$1 -d是判断$!这个目录存不存在
所以你要穿的参数如果是正确的文件目录名,则此时的判断值为0 即真
2 帮你拓展下:
[ -f 判断参数 ] 判断一个普通文件是不是存在
[ -s 文件名 ] 文件存在且内容非空即必须要一个空格以上的字符
[ -e 文件目录 ] 文件目录下是否不为空
$@ :以("$1""$2"...)的形式保存所有输入的命令行参数;
$? :上一条命令的返回结果.
我们先写一个简单的脚本,执行以后再解释各个变量的意义
# touch variable
# vi variable
脚本内容如下:
#!/bin/sh
echo "number:$#"
echo "scname:$0"
echo "first :$1"
echo "second:$2"
echo "argume:$@"
echo "show parm list:$*"
echo "show process id:$$"
echo "show precomm stat: $?"
保存退出
赋予脚本执行权限
# chmod +x variable
执行脚本
# ./variable aa bb
number:2
scname:./variable
first:aa
second:bb
argume:aa bb
show parm list:aa bb
show process id:24544
show precomm stat:0
通过显示结果可以看到:
$# 是传给脚本的参数个数
$0 是脚本本身的名字
$1 是传递给该shell脚本的第一个参数
$2 是传递给该shell脚本的第二个参数
$@ 是传给脚本的所有参数的列表
$* 是以一个单字符串显示所有向脚本传递的参数,与位置变量不同,参数可超过9个
$$ 是脚本运行的当前进程ID号
$? 是显示最后命令的退出状态,0表示没有错误,其他表示有错误
#!/bin/bash BASHNAME=test.sh BINPATH=$0 BINNAME=./cycle gnome-terminal -x "${BINPATH/$BASHNAME/$BINNAME}" exit 0
########################################################################################### # cd to bash dir ########################################################################################### link=`readlink -f $0` echo "$link" dirName=`dirname $link` echo "$dirName" cd $dirName; echo "now dir In :$(pwd) ";
for name in `find ../../src/ -regex '.*cpp\|.*cc\|.*c' -exec basename {} \;`; do gcov_cmd=`find ../../ -name "*${name}.gcno"`; gcov_out=`gcov ${gcov_cmd} | grep -A 1 ${name}`; percentageNum=`echo $gcov_out | cut -d':' -f2 | cut -d'%' -f1` if [ $(echo "$percentageNum > 80.00" | bc) -eq 1 ]; then echo "\033[32m $gcov_out \033[0m" else echo "\033[31m $gcov_out \033[0m" fi done