语法基本介绍
1.1 开头
#!/bin/sh 程序必须以下面的行开始(必须方在文件的第一行): 符号#!用来告诉系统它后面的参数是用来执行该文件的程序。
1.2 注释
以#开头的句子表示注释
1.3 变量
"=" 左右两边都不能有空格; 语句结尾不需要分号 引用变量进来使用${var}
1.4 整数计算
有如下几种:" + - * / % " 在*和/之前必须冠以反斜线,以防被SHELL先行解释。 一般通过 let 和 expr 这两个指令来实现 如对变量 x 加 1 可以写作: let "x = $x + 1" 或者 x=`expr $x + 1`
1.5 命令行参数
$# 传入脚本的命令行参数个数; $* 所有命令行参数值,在各个参数值之间留有空格; $0 命令本身(shell文件名) $1 第一个命令行参数; $2 第二个命令行参数;
1.6 局部变量
变量首次被赋初值时加上 local 关键字就可以声明一个局部变量 #!/bin/bash HELLO="var1" echo $HELLO function hello { local HELLO="var2" echo $HELLO } echo $HELLO 该程序的执行结果是: var1 var2 var1
1.7 BASH变量与C语言变量的区别
BASH 中的变量在引用时都需要在变量前加上 "$" 符号( 第一次赋值及在For循环的头部不用加 "$"符号 ); BASH 中没有浮点运算,因此也就没有浮点类型的变量可用; BASH 中的整形变量的比较符号与 C 语言中完全不同,而且整形变量的算术运算也需要经过 let 或 expr 语句来处理;
1.8 环境变量
由export关键字处理过的变量叫做环境变量
Shell命令和流程控制 (在shell脚本中可以使用三类命令)
2.1 Unix 命令
echo "some text": 将文字内容打印在屏幕上 ls: 文件列表 wc –l file; wc -w file; wc -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 file.txt: 输出文件内容到标准输出设备(屏幕)上 file somefile: 得到文件类型 read var: 提示用户输入,并将输入赋值给变量 sort file.txt: 对file.txt文件中的行进行排序 uniq: 删除文本文件中出现的行列比如: sort file.txt | uniq expr: 进行数学运算. Example: add 2 and 3 expr 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是一个基本的查找替换程序。 比如:将linuxfocus 替换为 LinuxFocus : cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。 cat file.txt | awk -F,'{print $1 "," $3 }'
2.2 概念: 管道,重定向和 backtick
略
2.3 使用反短斜线(` `)可以将一个命令的输出作为另外一个命令的一个命令行参数。
命令: find . -mtime -1 -type f -print 用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。 如果您想将所有查找到的文件打一个包,则可以使用以下脚本: #!/bin/sh # The ticks are backticks (`) not normal quotes ('): tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print`
2.4 流程控制
if ....; then .... elif ....; then .... else .... fi 通常用" [ ] "来表示条件测试。 注意这里的空格很重要。要确保方括号的空格。 [ -f "somefile" ] :判断是否是一个文件 [ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限 [ -n "$var" ] :判断$var变量是否有值 [ "$a" = "$b" ] :判断$a和$b是否相等
2.5 文件的操作: 含义( 满足下面要求时返回 TRUE )
-e 文件已经存在 -f 文件是普通文件 -s 文件大小不为零 -d 文件是一个目录 -r 文件对当前用户可以读取 -w 文件对当前用户可以写入 -x 文件对当前用户可以执行 -g 文件的 GID 标志被设置 -u 文件的 UID 标志被设置 -O 文件是属于当前用户的 -G 文件的组 ID 和当前用户相同 {{{code file1 -nt file2 文件 file1 比 file2 更新 file1 -ot file2 文件 file1 比 file2 更老 code}}} 如 if [ -x /root ] 可以用于判断 /root 目录是否可以被当前用户进入 执行man test可以查看所有测试表达式可以比较和判断的类型。
2.6 变量的比较操作
相同 -eq = 不同 -ne != 大于 -gt > 小于 -lt < 大于或等于 -ge 小于或等于 -le 为空 -z 不为空 -n 比如: 比较整数 a 和 b 是否相等就写做 if [ $a = $b ] 判断整数 a 是否大于整数 b 就写做 if [ $a -gt $b ] 比较字符串 a 和 b 是否相等就写作:if [ $a = $b ] 判断字符串 a 是否为空就写作: if [ -z $a ] 判断整数变量 a 是否大于 b 就写作:if [ $a -gt $b ] 注意:在“[”和“]”符号的左右都留有空格。
2.7 快捷操作符
熟悉C语言的朋友可能会很喜欢下面的表达式: [ -f "/etc/shadow" ] && echo "This computer uses shadow passwors" 这里 && 就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。 {{{code #!/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表达式作任何事情,但是使用与或操作符会更便利很多。 code}}}
2.8 case
case :表达式可以用来匹配一个给定的字符串,而不是数字。case "$var" in condition1 ) do_thing_1;; condition2 ) do_thing_2;; * ) default statments;; esac #!/bin/bash echo "Hit a key,then hit return." read Keypress case "$Keypress" in [a-z] ) echo "Lowercase letter";; [A-Z] ) echo "Uppercase letter";; [0-9] ) echo "Digit";; * ) echo "Punctuation,whitespace,or other";; esac exit 0 该脚本可以自动解压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
2.9 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
2.10 loop表达式:
#while-loop while ...; do .... done #for-loop for var in [list]; do .... done # for-loop example #!/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.
2.11 until
until 循环的基本结构是:
until [ condition is TRUE ] do #code block done
2.12 引号
单引号更严格一些。它可以防止任何变量扩展。 双引号可以防止通配符扩展但允许变量扩展。 还有一种防止这种扩展的方法,那就是使用转义字符——反斜杆: echo \$SHELL 这将输出:$SHELL
实例
3.1 二进制到十进制的转换
#!/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 } #return the last character of a string in $rval # lastchar() { 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` } # remove the last character in string and return it in $rval # chop() { 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 –c计算字符个数,然后使用cut命令取出末尾一个字符。
3.2 文件循环程序
或许您是想将所有发出的邮件保存到一个文件中的人们中的一员,但是在过了几个月 以后,这个文件可能会变得很大以至于使对该文件的访问速度变慢。下面的 脚本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命令。
shell也有一个真实的调试模式。如果在脚本”strangescript” 中有错误,您可以这样来进行调试:
sh -x strangescript
这将执行该脚本并显示所有变量的值。
shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用:
sh -n your_script
这将返回所有语法错误。关于bash在控制台下的快捷键
ctrl+u 删除光标以前的所有字符 ctrl+d 删除光标以前的一个字符 ctrl+k 删除光标以后的所有字符 ctrl+h 删除光标以后的一个字符 ctrl+t 调换光标前两个字符的次序 ctrl+a 移动光标到最前面 ctrl+e 移动光标到最后面 ctrl+p 上一个命令 ctrl+n 下一个命令 ctrl+s 锁定输入 ctrl+q 解除锁定 ctrl+f 移动光标到后一个字符 ctrl+b 移动光标到前一个字符 ctrl+x 标记一个位置 ctrl+c 清除当前的输入
最基本的理论基础
5.1 stdin(标准输入),stdout(标准输出),stderr(标准错误输出)(std=standard).
1.重定向stdout到一个文件 2.重定向stderr到一个文件 3.重定向stdout到stderr 4.重定向stderr到stdout 5.重定向stderr到stdout中并且成为一个文件 6.重定向stderrandstdouttostdout 7.重定向stderrandstdouttostderr 在Linux中1代表标准输出,2代表'标准错误' 标准输出 这个例子将会使ls的显示结果重定向到一个文件中. ls-l>ls-l.txt 标准错误 这个例子将会使grep命令在运行过程中出现的错误输出到一个文件中 grepda*2>grep-errors.txt
5.2 管道
为什么会用到管道 - 管道可以使你非常方便的将一个程序的结果转向到另外一个程序中。 一个sed的例子,这个例子使用了非常简单的管道功能: ls-l|sed-e"s/[aeio]/u/g" 执行以下命令后: 首先ls–l会先执行并且它会输出结果信息但是如果它的后面跟是一个管道符的话,那么它就会将结果重新定向到sed这个程序中,sed使用了替换功能, 所以这个例子执行完会,会将ls–l结果中所有含有aeio的英文单词替换成单词u. 通过另外的方法实现ls–l*.txt 也许这种方法不同于ls–l*.txt,但是它避免了出现一条NoSuchfileOrDirectory这种信息。 ls-l|grep".txt" 当ls–l执行后,它会将程序结果输出到grep这个程序中,并且去匹配.txt这条信息。jiao