
序:我们现在使用的是什么shell?
一. shell分类
几种常见shell简介
Linux系统提供多种不同的Shell以供选择。常用的有Bourne Shell(简称sh)、C-Shelll(简称csh)、Korn Shell(简称ksh)和Bourne Again Shell (简称bash)。
(1)Bourne Shell是AT&T Bell实验室的 Steven Bourne为AT&T的Unix开发的,它是Unix的默认Shell,也是其它Shell的开发基础。Bourne Shell在编程方面相当优秀,但在处理与用户的交互方面不如其它几种Shell。
(2)C Shell是加州伯克利大学的Bill Joy为BSD Unix开发的,与sh不同,它的语法与C语言很相似。它提供了Bourne Shell所不能处理的用户交互特征,如命令补全、命令别名、历史命令替换等。但是,C Shell与BourneShell并不兼容。
(3)Korn Shell是AT&T Bell实验室的David Korn开发的,它集合了C Shell和Bourne Shell的优点,并且与Bourne Shell向下完全兼容。Korn Shell的效率很高,其命令交互界面和编程交互界面都很好。
(4)Bourne Again Shell (即bash)是自由软件基金会(GNU)开发的一个Shell,它是Linux系统中一个默认的Shell。Bash不但与Bourne Shell兼容,还继承了C Shell、Korn Shell等优点。
二. 一个Bash例程
1. 编写并保存以下代码
例程 1.1 清空日志文件
#!/bin/bash
# 一个Bash脚本的正确的开头部分.
LOG_DIR=/var/log
# 如果使用变量,当然比把代码写死的好.
cd $LOG_DIR
cat /dev/null > messages
cat /dev/null > wtmp
echo "Logs cleaned up."
exit # 这个命令是一种正确并且合适的退出脚本的方法.
例程 1.2 以下是上面例程的改进代码段
1 #!/bin/bash
2 # 清除, 版本 3
3
4 # 警告:
5 # -----
6 # 这个脚本有好多特征,
7 #+ 这些特征是在后边章节进行解释的.
8 # 大概是进行到本书的一半的时候,
9 #+ 你就会觉得它没有什么神秘的了.
10
11
12
13 LOG_DIR=/var/log
14 ROOT_UID=0 # $UID为0的时候,用户才具有root用户的权限
15 LINES=50 # 默认的保存行数
16 E_XCD=66 # 不能修改目录?
17 E_NOTROOT=67 # 非root用户将以error退出
18
19
20 # 当然要使用root用户来运行.
21 if [ "$UID" -ne "$ROOT_UID" ]
22 then
23 echo "Must be root to run this script."
24 exit $E_NOTROOT
25 fi
26
27 if [ -n "$1" ]
28 # 测试是否有命令行参数(非空).
29 then
30 lines=$1
31 else
32 lines=$LINES # 默认,如果不在命令行中指定.
33 fi
34
35
36 # Stephane Chazelas 建议使用下边
37 #+ 的更好方法来检测命令行参数.
38 #+ 但对于这章来说还是有点超前.
39 #
40 # E_WRONGARGS=65 # 非数值参数(错误的参数格式)
41 #
42 # case "$1" in
43 # "" ) lines=50;;
44 # *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
45 # * ) lines=$1;;
46 # esac
47 #
48 #* 直到"Loops"的章节才会对上边的内容进行详细的描述.
49
50
51 cd $LOG_DIR
52
53 if [ `pwd` != "$LOG_DIR" ] # 或者 if[ "$PWD" != "$LOG_DIR" ]
54 # 不在 /var/log中?
55 then
56 echo "Can't change to $LOG_DIR."
57 exit $E_XCD
58 fi # 在处理log file之前,再确认一遍当前目录是否正确.
59
60 # 更有效率的做法是:
61 #
62 # cd /var/log || {
63 # echo "Cannot change to necessary directory." >&2
# exit $E_XCD;
65 # }
66
67
68
69
70 tail -$lines messages > mesg.temp # 保存log file消息的最后部分.
71 mv mesg.temp messages # 变为新的log目录.
72
73
74 # cat /dev/null > messages
75 #* 不再需要了,使用上边的方法更安全.
76
77 cat /dev/null > wtmp # ': > wtmp' 和 '> wtmp'具有相同的作用
78 echo "Logs cleaned up."
79
80 exit 0
81 # 退出之前返回0,
82 #+ 返回0表示成功.
例程 1.3 显示日期、时间
1 #!/bin/bash
2 # 练习'date'命令
3
4 echo "The number of days since the year's beginning is `date +%j`."
5 # 需要在调用格式的前边加上一个'+'号.
6 # %j用来给出今天是本年度的第几天.
7
8 echo "The number of seconds elapsed since 01/01/1970 is `date +%s`."
9 # %s将产生从"UNIX 元年"到现在为止的秒数,
10 #+ 但是这东西现在还有用么?
11
12 prefix=temp
13 suffix=$(date +%s) # 'date'命令的"+%s"选项是GNU特性.
14 filename=$prefix.$suffix
15 echo $filename
16 # 这是一种非常好的产生"唯一"临时文件的办法,
17 #+ 甚至比使用$$都强.
18
19 # 如果想了解'date'命令的更多选项, 请查阅这个命令的man页.
20
21 exit 0
2. 执行
编写完脚本之后,你可以使用sh scriptname, [1] 或者bash scriptname来调用这个脚本. (不推荐使用sh chmod 555 scriptname (允许任何人都具有可读和执行权限) [2] 或者 chmod +rx scriptname (允许任何人都具有可读和执行权限) chmod u+rx scriptname (只给脚本的所有者可读和执行权限) 既然脚本已经具有了可执行权限,现在你可以使用: ./scriptname 来测试这个脚本了. 如果这个脚本以一个"#!"行开头, 那么脚本将会调用合适的命令解释器来运行. 三. 一个TC Shell例程 例1:变量定义与从键盘输入变量的值 #!/bin/tcsh set var_new = "hahaha lolo" echo vbvbvb $var_new set var_b=$< echo "user input $var_b"...... 注意:执行时应使用以下方式: /bin/tcsh scriptname 变量 输入/输出、重定向 使程序在后台/前台运行 内置参数 练习 一. 变量的定义、赋值、引用、删除 echo:显示变量的内容 env:显示系统所有环境变量 set:显示系统所有变量 export:导出变量使之在子进程中也可以使用 unset:删除变量 例2-1. 变量的赋值与替换 #!/bin/bash # 变量赋值和替换 a=375 hello=$a #------------------------------------------------------------------------- # 强烈注意, 在赋值的的时候, 等号前后一定不要有空格.考虑如果出现空格会怎么样? #------------------------------------------------------------------------- echo hello # 没有变量引用, 只是个hello字符串. echo $hello echo ${hello} # 同上. echo "$hello" echo "${hello}" echo hello="A B C D" echo $hello # A B C D echo "$hello" # A B C D # 就象你看到的echo $hello 和 echo "$hello" 将给出不同的结果. echo '$hello' # $hello # 全引用的作用将会导致"$"被解释为单独的字符,而不是变量前缀. hello= # 设置为空值. echo "\\$hello (null value) = $hello" # 注意设置一个变量为null, 与unset这个变量, 并不是一回事,虽然最终的结果相同(具体见下边). # -------------------------------------------------------------- echo; echo numbers="one two three" # ^ ^ other_numbers="1 2 3" # ^ ^ # 如果在变量值中存在空白, 那么就必须在赋值时加上引用. # other_numbers=1 2 3 # 将给出一个错误消息. echo "numbers = $numbers" echo "other_numbers = $other_numbers" # other_numbers = 1 2 3 echo "uninitialized_variable = $uninitialized_variable" # Uninitialized变量为null(就是没有值). uninitialized_variable= # 声明, 但是没有初始化这个变量, #其实和前边设置为空值的作用是一样的. echo "uninitialized_variable = $uninitialized_variable" # 还是一个空值. uninitialized_variable=23 # 赋值. unset uninitialized_variable # Unset这个变量. echo "uninitialized_variable = $uninitialized_variable" # 还是一个空值. echo exit 0 例2-2. 变量赋值 #!/bin/bash # 等号赋值 a=879 echo "The value of \\"a\\" is $a." # 使用'let'赋值 let a=16+5 echo "The value of \\"a\\" is now $a." echo 'The value of \\'a\\' is now $a.' # 在'for'循环中赋值(事实上, 这是一种伪赋值): echo -n "Values of \\"a\\" in the loop are: " #-------------------------------------------- for a in 7 8 9 11 do echo -n "$a " done echo #-------------------------------------------- #使用'read'命令进行赋值(这也是一种赋值的类型): echo -n "Enter \\"a:\\" " read a echo "The value of \\"a\\" is now $a." #-------------------------------------------- b=$a echo $b # 现在让我们来点小变化(命令替换). c=`echo Hello!` # 把'echo'命令的结果传给变量'a' echo $c c=`ls -l` # 把'ls -l'的结果赋值给'a' echo $c #然而, 如果没有引号的话将会删除ls结果中多余的tab和换行符. echo "$c" #如果加上引号的话, 那么就会保留ls结果中的空白符. #命令替换也可以通过( )实现 R=$(cat /etc/redhat-release) arch=$(uname -m) echo $R; echo $arch exit 0 例2-3. 进行浮点运算 #!/bin/bash echo -n "Inpute a:" read a echo -n "Input b:" read b #将文件afile中保存的运算交由bc执行 n=`bc < afile` #n=`cat afile | bc` echo $n #计算a,b的乘积,包括浮点数 c=`echo "scale=3;$a*$b" | bc` _____________________________________________ 文件afile的内容就是bc可以接受的运算及处理 #cat afile scale=3 for(i=1;i<5;i++){j=j+i} 二. 输入/输出、重定向: > 、 < 、 | 、 1> 、2> 1. 文件描述符:与某个打开的文件相关联的数字(相当于C语言是的文件号) 1) 要关闭某文件描述符,使用: exec n>&- 2) 将数据同时输出到标准输出设备(屏幕) 和文件,需要使用tee. tee的语法如下: tee [option]... [file]... options: -a --将输出追加到文件后 -i --忽略中断信号 2.示例 $cat #cat把键盘看作标准输入,屏幕看作标准输出。按下CTRL+D结束键盘输入 $cat > sample.txt $cat /dev/null > /var/log/messages $cat x>hold $cat x y>hold $cat x y | tr "[a-z]" "[A-Z]" $cat x y 1>hold1 2>hold2 $ls | lpr $who | sort $ls | less $exec 1>fd1.out #将以后所有命令的输出都定向到fd1.out $cmd 1>file $cmd 1>>file $cmd 1>file1 2>file2 $ln -s ch05.doc ./docs >> /tmp/ln.log 2>/dev/null $rm -rf /tmp/my_tmp_dir > /dev/null 2>&1 $who | tee file.a | wc -l 例:2-4 将循环的输出重新排序 #!/bin/bash for i in 7 9 2 4 5 12 do echo $i done | sort -n exit 0 例:2-5 输入重定向(利用read读入文件/etc/fstab的前两行) #!/bin/bash # 从/etc/fstab中读行. File=/etc/fstab { read line1 read line2 } < $File echo "First line in $File is:" echo "$line1" echo "Second line in $File is:" echo "$line2" exit 0 例2-6:每5分钟将将登录进入系统的用户列表追加到logfile文件中 #!/bin/bash while : do date who sleep 300 done >> logfile 例2-7:将一个文件描述符输出到另一个文件描述符 #!/bin/bash exec 4>out.txt exec 5>&4 date 1>&5 例2-8: 将所有查找到的文件(在工作目录下过去24小时内修改过的文件)打一个包 #!/bin/bash tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print` 三. 变量置换 ${value:offset:length} ${value##pattern} ${value%%pattern} ${value//pattern/string} myfruit="pear" fruit=${myfruit:-apple} echo When myfruit is set ,fruit is :$fruit unset myfruit fruit=${myfruit:-apple} echo When myfruit is unset ,fruit is :$fruit -------------------------------------------------- unset var_x echo When var_x is unset ,var_x is :$var_x echo Now var_x is: ${var_x:=shalala} ------------------------------------------------- unset var_x ${var_x:?"The var_x is undefined"} 或 ${HOME:?"Your home directory is undefined"} ------------------------------------------------- var_x="beijing 2008 " echo ${var_x:+"aoyun beijing"} echo var_x is :$var_x unset var_x echo ${var_x:+"aoyun beijing"} echo var_x is :$var_x 练习: 1.如果变量MYPATH没有被赋值,那么下面两个命令会执行怎样的动作,有何不同 ${MYPATH:=/usr/bin:/usr/sbin;/usr/ucb} ${MYPATH:-/usr/bin:/usr/sbin;/usr/ucb} 2.将当前目录下以某些字符开头的文件名,更改成以其它字符(参照Bash Shell编程手册之例9-19--修改文件扩展名) 3.若变量a="beijing 2008",请用变量内容替换的方法将beijing更换为paris,2008替换为2012 四. 在前台/后台运行程序(fg、bg)、显示当前作业列表(jobs) 教材 P235 例2-9:在后台运行某命令 $ls -l | lpr & CONTROL+Z :将作业从前台移到后台 例2-10:将3号作业移到前台 $fg 3 例2-11:将3号作业移到后台 $bg 3 例2-12:终止某个作业 $ps | grep XXX #找到作业的进程号PID $kill n 五. BASH内部命令参考 $info bash 六. 位置参量和命令行参数 例2-13:命令行参数的简单示例 #!/bin/bash echo "$0 $1 and $2" echo "The number of para is $#" set Jake Nicky Scott echo "Para number is :$#All the para is :$*" 特殊参数: $$: shell进程的PID号 $!: 后台运行的进程的PID号 $?: 退出状态 例2-14:用shift移动参数 #!/bin/bash until [ -z "$1" ] # 直到所有的位置参数都被存取完... do echo -n "$1 " shift done echo exit 0 例 2-15 getopts处理命令行选项 #!/bin/bash while getopts xy options 2>/dev/null do case $options in x) echo "you entered x ";; y) echo "you entered y" ;; \\?) echo "only -x and -y are valid options " 1>&2 ;; esac done exit 0 例 2-16 getopts 与$OPTARG #!/bin/bash while getopts dq: options do case $options in d) echo "you entered d ";; q) echo "the argument for -q is $OPTARG" ;; \\?) echo "Usage:opts3 -dq filename... " 1>&2 ;; esac done exit 0 . 课堂练习 2_1. 编写bash脚本:定义两个变量,从键盘给它们输入数字值,屏幕输出两个变量相加的结果。 2_2. 编写bash脚本:将前一天修改过的文件名按字母顺序保存在用户主目录的一个文件中;将当前目录下所有以a或A打头的文件设置为可运行(find ... -exec ...)。 2_3.编写脚本,用read逐行读取某文件,并输出. 2_4.利用脚本参数的方法,将执行脚本时指定的两个参数文件合并到第三个参数指定的文件中.(即通过./main a b c,能将文件a,文件b的内容合并输出到文件c中) 2_5.说明下一段代码的执行结果: exec 4>o.txt exec 5>&4 exec 1>&5 date 其它. 附:bash内置参数 PPID : 该bash的呼叫者process ID. PWD : 目前的工作目录。 OLDPWD : 上一个工作目录。 REPLY : 当read命令没有参数时,直接设在REPLY上。 UID : User ID。 EUID : Effective User ID。 BASH : Bash的完整路径。 BASH_VERSION : Bash版本。 SHLVL : 每次有Bash执行时,数字加一。 RANDOM : 每次这个参数被用到时,就会产生一个乱数在RANDOM上。 SECONDS : 从这个Shell一开始启动後的时间。 LINENO : Script的行数。 HISTCMD : 历史记录数。 OPTARG : getopts处理的最後一个选项参数。 OPTIND : 下一个要由getopts所处理的参数号码。 HOSTTYPE : 机器种类。 OSTYPE : 作业系统名称。 IFS : Internal Field Separator。 PATH : 命令搜寻路径。 PATH="/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin:." HOME : 目前使用者的home directory; CDPATH : cd命令的搜寻路径。 ENV : 如果这个参数被设定,每次有shell script被执行时,将会执行它所设定的档名做为环境设定。 MAIL : 如果这个参数被设定,而且MAILPATH没有被设定,那麽有信件进来时,bash会通知使用者。 MAILCHECK : 设定多久时间检查邮件一次。 MAILPATH : 一串的邮件检查路径。 MAIL_WARNING : 如果有设定的话,邮件被读取後,将会显示讯息。 PS1 : 提示讯息设定,内定为"bash$ "。(请详见提示讯息一节。) PS2 : 第二提示讯息设定,内定为"> "。 PS3 : select命令所使用的提示讯息。 PS4 : 执行追踪时用的提示讯息设定,内定为"+ "。 HISTSIZE : 命令历史记录量,内定为500。 HISTFILE : 历史记录档,内定~/.bash_history。 HISTFILESIZE : 历史记录档行数最大值,内定500。 OPTERR : 如果设为1,bash会显示getopts的错误。 PROMPT_COMMAND : 如果设定的话,该值会在每次执行命令前都显示。 IGNOREEOF : 将EOF值当成输入,内定为10。 TMOUT : 如果设为大於零,该值被解译为输入等待秒数。若无输入,当成没有输入。 FCEDIT : fc命令的内定编辑器。 FIGNORE : 请详见READLINE。 INPUTRC : readline的startup file,内定~/.inputrc notify : 如果设定了,bash立即报告被终结的背景程式。 history_control, HISTCONTROL : history使用。 command_oriented_history : 存入多行指令。 glob_dot_filenames : 如果设定了,bash将会把"."包含入档案路径中。 allow_null_glob_expansion : 如果设定了,bash允许路径明称为null string。 histchars : history使用。 nolinks : 如果设定了,执行指令时,不会跟随symbolic links。 hostname_completion_file, HOSTFILE : 包含与/etc/hosts相同格式的档名。 noclobber : 如果设定了,Bash不会覆写任何由">"、">&"及"<>"所操作的档案。 auto_resume : 请见任务控制一节。 no_exit_on_failed_exec : 如果该值存在,非互动的shell不会因为exec失败而跳出。 cdable_vars : 如果启动,而cd命令找不到目录,可切换到参数形态指定的目录下。 一.条件 返回 1. if语句 if ....; then .... elif ....; then .... else .... fi 2.case 分支 case ... in ...) do something here esac 大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等... 通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。 [ -f "somefile" ] :判断是否是一个文件 [ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限 [ -n "$var" ] :判断$var变量是否有值 [ "$a" = "$b" ] :判断$a和$b是否相等 [ ] :条件测试. 条件测试表达式放在[ ]中. [[ ]]:是一个扩展的"[ ]"命令,[[ ]]结构比[ ]结构更加通用. 使用[[ ... ]]条件判断结构, 而不是[ ... ], 能够防止脚本中的许多逻辑错误. 比如, &&, ||, <, 和> 操作符能够正常存在于[[ ]]条件判断结构中, 但是如果出现在[ ]结构中的话, 会报错. (( )) :整数扩展. 扩展并计算在(( ))中的整数表达式. 双圆括号结构也被认为是在Bash中使用C语言风格操作变量的一种处理机制. 文件测试操作符 如果下面的条件成立将会返回真. -e 文件存在 -a 文件存在,这个选项的效果与-e相同. 但是它已经被"弃用"了, 并且不鼓励使用. -f 表示这个文件是一个一般文件(并不是目录或者设备文件) -s 文件大小不为零 -d 表示这是一个目录 -b 表示这是一个块设备(软盘, 光驱, 等等.) -c 表示这是一个字符设备(键盘, modem, 声卡, 等等.) -p 这个文件是一个管道 -h 这是一个符号链接 -L 这是一个符号链接 -S 表示这是一个socket -t 文件(描述符)被关联到一个终端设备上,这个测试选项一般被用来检测脚本中的stdin([ -t 0 ]) 或者stdout([ -t 1 ])是否来自于一个终端. -r 文件是否具有可读权限(指的是正在运行这个测试命令的用户是否具有读权限) -w 文件是否具有可写权限(指的是正在运行这个测试命令的用户是否具有写权限) -x 文件是否具有可执行权限(指的是正在运行这个测试命令的用户是否具有可执行权限) -g set-group-id(sgid)标记被设置到文件或目录上,如果目录具有sgid标记的话, 那么在这个目录下所创建的文件将属于拥有这个目录的用户组, 而不必是创建这个文件的用户组. 这个特性对于在一个工作组享目录非常有用. -u set-user-id (suid)标记被设置到文件上,如果一个root用户所拥有的二进制可执行文件设置了set-user-id标记位的话, 那么普通用户也会以root权限来运行这个文件. [1] 这对于需要访问系统硬件的执行程序(比如pppd和cdrecord)非常有用. 如果没有suid标志的话, 这些二进制执行程序是不能够被非root用户调用的. -k 设置粘贴位 对于"粘贴位"的一般了解, save-text-mode标志是一个文件权限的特殊类型. 如果文件设置了这个标志, 那么这个文件将会被保存到缓存中, 这样可以提高访问速度. [2] 粘贴位如果设置在目录中, 那么它将写权限. 对于设置了粘贴位的文件或目录, 在它们的权限标记列中将会显示t. -O 判断你是否是文件的拥有者 -G 文件的group-id是否与你的相同 -N 从文件上一次被读取到现在为止, 文件是否被修改过 f1 -nt f2 文件f1比文件f2新 f1 -ot f2 文件f1比文件f2旧 f1 -ef f2 文件f1和文件f2是相同文件的硬链接 ! "非" -- 反转上边所有测试的结果(如果没给出条件, 那么返回真). 整数比较操作符 -eq 等于 如: if [ "$a" -eq "$b" ] -ne 不等于 if [ "$a" -ne "$b" ] -gt 大于 if [ "$a" -gt "$b" ] -ge 大于等于 if [ "$a" -ge "$b" ] -lt 小于 if [ "$a" -lt "$b" ] -le 小于等于 if [ "$a" -le "$b" ] < 小于(在双括号中使用) (("$a" < "$b")) <= 小于等于(在双括号中使用) (("$a" <= "$b")) > 大于(在双括号中使用) (("$a" > "$b")) >= 大于等于(在双括号中使用) (("$a" >= "$b")) 字符串比较操作符 = 等于 if [ "$a" = "$b" ] == 等于 if [ "$a" == "$b" ],与=等价. != 不等号 if [ "$a" != "$b" ],这个操作符将在[[ ... ]]结构中使用模式匹配. < 小于, 按照ASCII字符进行排序,if [[ "$a" < "$b" ]],if [ "$a" \\< "$b" ],注意"<"使用在[ ]结构中的时候需要被转义. > 大于, 按照ASCII字符进行排序 if [[ "$a" > "$b" ]],if [ "$a" \\> "$b" ],注意">"使用在[ ]结构中的时候需要被转义. -z 字符串为"null", 意思就是字符串长度为零 -n 字符串不为"null". 例3-1:判断输入的用户名是否存在 #!/bin/bash echo -n "Please input user name:" read name grep "$name" /etc/passwd > /dev/null 2>&1 if [ $? -eq 0 ] ; then echo $name exist exit 0 else echo $name not exist exit 1 fi 例3-2:一个简单的条件例程 #!/bin/bash echo -n "Are you ok?" read ans if [ "$ans" = "Y" -o "$ans" = "y" ]; then echo "Glad to hear it" elif [ "$ans" = "N" -o "$ans" = "n" ]; then echo "then let us wait a minute" else echo "what is your mean?" fi exit 0 例3-3:查找30天以内修改过的、大小大于某值的文件 #!/bin/bash if (( $# != 2 )); then echo "Usage: $0 modi_days size " 1>&2 exit 1 fi if (( $1 < 0 || $1 > 30 )) ; then echo "modi_days is out of range" exit 2 fi if (( $2 <= 20 )) ; then echo "The size is out of range" exit 3 fi find ~ -xdev -mtime $1 -size +$2 exit 0 例3-4:判断某个文件的属性 #!/bin/bash echo -n "Input file name:" read file if [ -d $file ]; then echo "$file is a directory " elif [ -f $file ] ; then if [ -r $file -a -w $file -a -x $file ] ; then echo "you have read write and execute permission on $file." fi else echo "$file is neither a file nor a directory" fi exit 0 例3-5:下面的脚本可以自动解压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" *) error "File $1 can not be uncompressed with smartzip";; esac 二.循环 返回 需掌握以下内容: for 结构循环 while 结构循环 untile 结构循环 select 结构循环 循环嵌套 break,continue 跳出循环,继续循环 例3-6:显示所有命令行参数 #!/bin/bash for i in "$@" #$* do echo $i done exit 0 例3-7:给多个人发送同一封信 $cat mylist commy patty anny jack $cat mailer #!/bin/bash for person in $(cat mylist) do mail $person < letter echo $person was sent a letter done exit 0 例3-8:为多个文件建立备份 #!/bin/bash dir /home/jody/backupscripts for file in memo[1-5] do if [ -f $file ] ; then cp $file $dir/$file.bak echo “$file in backed up in $dir” fi done exit 0 例3-9. 在目录的所有文件中查找源字串 #!/bin/bash # findstring.sh: # 在一个指定目录的所有文件中查找一个特定的字符串. directory=/usr/bin/ fstring="Free Software Foundation" # 查看哪个文件中包含FSF. for file in $( find $directory -type f -name '*' | sort ) do strings -f $file | grep "$fstring" | sed -e "s%$directory%%" done exit 0 例3-10: 生成菜单 #!/bin/bash PS3="Select a program to execute:" select program in "ld -F" pwd date do $program done exit 0 例3-11: 菜单与分支 #!/bin/bash PS3="Please select one of the three boys:" select choice in Tom dan guy "Exit loop" do case $choice in Tom) echo $choice is good;; dan) echo $choice is normal;; guy) echo $choice is passed;; "Exit loop") echo $choice;break;; *) echo $choice is wrong esac done exit 0 例3-12: 在后台执行循环 #!/bin/bash for person in bob jim sam smith do mail $person < memo done & exit 0 例3-13: 内部字段分隔符IFS与循环的例子 #!/bin/bash names=Tome:Dick:Harry:John oldifs="$IFS" IFS=":" for person in $names do echo Hi $person done set Jill Jane Jolens IFS="$oldifs" for girl in $* do echo Howdy $girl done exit 0 例3-14: 操作一组文件--复制到指定目录并更改文件权限 #!/bin/bash for FILE in $HOME/.bash* do cp $FILE ${HOME}/public_html chmod a+r ${HOME}/public_html/${FILE} done exit 0 例3-15: 逐行处理文件的几种方式 #!/bin/bash echo -n "please input filename :" read filename if [ -f $filename ];then while read LINE do echo $LINE done < $filename else echo "$filename not exist" fi exit 0 #!/bin/bash echo -n "please input filename :" read filename if [ -f $filename ];then cat $filename | while read LINE do echo $LINE done else echo "$filename not exist" fi exit 0 #!/bin/bash echo -n "please input filename :" read filename if [ -f $filename ];then exec 3>&0 exec 0<$filename while read LINE do echo $LINE done exec 0<&3 else echo "$filename not exist" fi exit 0 练习 返回 3_1.编写脚本,能从键盘输入年龄,根据不同的年龄段,给出不同的提示信息。 3_2.编写脚本,能向httpd配置文件httpd.conf中添加一个虚拟主机的说明。 3_3.编写脚本addd,当执行./addd 4 5 6 7 8,能给出后面几个命令行参数4、5、6、7、8的和。 3_4.编写脚本,当执行脚本时,要求用户输入数据,只有输入”china”时,显示“输入正确”,并退出程序;输入其它数据,显示“输入错误”,并要求用户继续输入。 3_5.系统范围的脚本文件xinitrc可以用来启动X server. 这个文件包含了相当多的if/then条件测试, 下面是这个文件的部分节选. if [ -f $HOME/.Xclients ]; then exec $HOME/.Xclients elif [ -f /etc/X11/xinit/Xclients ]; then exec /etc/X11/xinit/Xclients else xclock -geometry 100x100-5+5 & xterm -geometry 80x50-50+150 & if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then netscape /usr/share/doc/HTML/index.html & fi fi 解释上边条件测试结构中的内容. 3_6.写脚本,能测试/usr/bin是一个目录还是一个符号链接 3_7.写一个select循环,列举出当前目录下的每个文件,并使用户通过选择文件序号来察看文件.除了列出每个文件,使用"Exit Program"作为退出循环的关键字.如果用户选择不是正规文件的选项,该程序应能发现问题.如果没有输入,该菜单应再次显示. :附加练习 1. 列出当前目录中所有的符号链接. 2. 以C语言风格--"(( ))",判断从键盘输入的整数是奇数还是偶数. 3. 接上题,要求可以反复从键盘输入数据,只到输入"Exit"退出程序 函数 ∙函数的定义 ∙执行函数 ∙函数参数 ∙全局变量、局部变量 ∙函数的链接,递归函数 ∙取消(清除)函数,导出函数 函数在当前shell的环境中执行,也就是说,函数执行时并不派生一个子进程. Bash shell先在别名中找,然后是函数,内置命令,最后才是可执行程序 一. 函数的定义 [function] func_name(){ ... } 二. 执行函数 通过函数名func_name来执行或调用函数 三. 函数参数 就象执行脚本时,可以附加参数一样,执行函数时也可以附加参数,并且也是通过$1、$2这样的形式访问的 四. 全局变量、局部变量 全局变量:有效范围是全局,即其值能在一个脚本的任何位置被访问。 局部变量:有效范围是局部,即其值只能在它被定义的函数(代码块)中使用,使用local或typeset来定义局部变量。 五. 函数的链接,递归函数 函数的链接:在一个函数中调用另一个函数。 递归函数:在函数中调用自己的函数。 六. 清除函数,导出函数 取消(清除)函数的定义,同删除变量一样,为:unset function_name 导出函数,为:export -f function_name 例 4-1:一个简单的带参函数 #!/bin/bash printMsg() { echo "$1:$2" } printMsg aaaa bbbb 例 4-2: 函数链接(在一个函数中调用另一个函数) #!/bin/bash orange (){ echo "Now in orange" banana } banana (){ echo "Now in banana" } orange 例 4-3 : 全局变量 #!/bin/bash pearFunc () { pear=2 echo "In pearFunc():pear is $pear" } pearFunc echo "Outside of pearFunc():pear is $pear" 例 4-4 : 全局变量 #!/bin/bash readPass () { pass="" echo -n "Enter password:" stty -echo read pass stty echo echo } readPass echo "password is: $pass" 例 4-5 : 局部变量 #!/bin/bash pearFunc () { typeset pear=2 # local pear=2 echo "In pearFunc():pear is $pear" } pearFunc echo "Outside of pearFunc():pear is $pear" 例 4-6 :递归 #!/bin/bash reverse () { if [ $# -gt 0 ];then typeset arg="$1" # local arg="$1" shift reverse "$@" echo "$arg" fi } reverse "$@" 例 4-7 :递归2,求阶乘 #!/bin/bash MAX_ARG=5 E_WRONG_ARGS=65 E_RANGE_ERR=66 if [ -z "$1" ];then echo "Usage:`basename $0` number" exit $E_WRONG_ARGS fi if [ "$1" -gt $MAX_ARG -o "$1" -lt "0" ];then echo "Out of range (5 is maxmum)." exit $E_RANGE_ERR fi function fact() { local number=$1 if [ "$1" -eq "0" ];then r=1 else let "n=$number-1" fact $n let "r=$number * $?" fi return $r } fact $1 echo "end of $1 is $?" exit 0 例 4-8 :函数库应用 #!/bin/bash #. ~/tysp2/appendixD/libtysp2.sh source $HOME/tysp2/appendixD/libtysp2.sh promptYESNO "If you want to call functions" "no" if [[ $YESNO -eq "y" ]]; then echo Free Space of /var/www is `getSpaceFree "/var/www"` echo Space used in /var/www/htm `getSpaceUsed "/var/www/html"` promptRESPONSE "please input prog name" "named" if [ ! -z RESPONSE ] ;then echo $RESPONSE pid is `getPID $RESPONSE` fi else echo "You dont want to do this" fi 练习 1. 写一个函数,判断一个命令是否存在于$PATH中指定的目录中。这个命令将作为第一个参数。如果存在的话,函数就输出命令的绝对路径并返回0。否则,函数返回1,并打印错误信息。 2. 增强本章中给出的readPass函数,使它能读两次密码,并能确认两次密码是一样的。 3. 编写函数,求一个整数的平方.整数从键盘输入,然后再调用函数. 4. 将前面循环中逐行处理文件的代码用函数包装起来,通过调用该函数的方式来逐行显示文件. 调试 1. 检查某脚本的语法 2. 执行并检查脚本的语法 3. 跟踪某段代码的执行 4. 跟踪递归函数的执行
注:文件描述符 代表的文件 0 标准输入文件 1 标准输出文件 2 标准错误输入文件 n 其它打开的文件
例:名称 语法 描述 缺省值置换 ${param:-word} 若param的值为空或未赋值, word取代param,否则返回param的值.但param的值不变 缺省值赋值 ${param:=word} 若param的值为空或未赋值,word 值被赋给param,,否则保持不变 空值错误 ${param:?msg} 若param的值为空或未赋值,将msg 信息输出到STDERR,并且退出shell 有值置换 ${param:+word} 若param有值,word取代param的值, 但param的值不变 提取子串 ${value:offset} 从变量中提取子串,这里offset和length可以是算术表达式. 获得变量字符个数 ${#value} 变量的字符个数 (变量的字符个数,并不是变量个数) 删除前部匹配 ${value#pattern} 去掉value中与pattern相匹配的部分,条件是value的开头与pattern相匹配, #与##的区别在于一个是最短匹配模式,一个是最长匹配模式. 删除尾部匹配 ${value%pattern} 与#,##类似,只是是从value的尾部与pattern相匹配 变量内容的替换 ${value/pattern/string} 进行变量内容的替换,把与pattern匹配的部分替换为string的内容,/与//的区别与上同
注: shift命令会重新分配位置参数, 其实就是把所有的位置参数都向左移动一个位置.位置参量 指代对象 $0 代表脚本名 $# 参数的个数 $* 列出所有位置参数 $@ 同上 "$*" 扩展为单个变量(例如:"$1$2$3") "$@" 扩展为多个单独的变量(例如:"$1 $1...${10}
练习名称 描述 bash -n 脚本名 解释但不执行脚本中的命令,通常用来进行语法检查 bash -v 脚本名 显示脚本中的所有行 bash -x 脚本名 在变量替换后(如果有的话),执行命令之前,显示该命令 set -x 跟踪脚本的执行 set +x 关闭脚本跟踪功能
