Download - 第 6 章 B-Shell 及编程
第 6 章 B-Shell 及编程
第 6章 第 2页
主要内容 主要目的
编写 shell脚本程序交互方式下使用 shell的流程控制,编写复合命令
主要内容元字符,如:引号shell的变量替换,命令替换,文件名生成shell变量流程控制子程序
第 6章 第 3页
主要特点 主要特点
shell编程风格和 C语言等算法语言的区别shell是面向命令处理的语言,提供的流程控
制结构通过对一些内部命令的解释实现如同 C语言设计思路一样, shell本身设计得
非常精炼,但是它提供了灵活的机制shell许多灵活的功能,通过 shell替换实现例如:流程控制所需的条件判断,四则运算,都由 shell之外的命令完成
6.1 启动 B-shell
第 6章 第 5页
启动交互式 B-shell 启动方法
注册 shell键入 sh 命令进入了 B-shell
自动执行的批处理文件当 sh 作为注册 shell 被启动时,自动执行用户主目录
下的 .profile 文件中命令,记作 $HOME/.profile类似 umask 之类的命令,应当写在 .profile 文件中
第 6章 第 6页
sh 脚本的执行 编辑文件 lsdir( 格式为文本文件 )
# ! /bin/shif [ $# = 0 ]then dir=.else dir=$1fifind $dir -type d –print
执行这一脚本 (script) 文件的方法 : sh<lsdirsh lsdirchmod u+x lsdir; ./lsdir /bin三种方法均启动程序 /bin/sh ,生成新进程
6.2 重定向与管道
第 6章 第 8页
输入重定向 <filename
从文件 filename 中获取 stdin例如 : sort < telno.txt
<<word 从 shell 获取数据直到再次遇到定界符 wordcat << TOAST* Now : `date`* My Home Directory is $HOMETOAST执行结果* Now : Sat Jul 27 14:47:56 Beijing 2006* My Home Directory is /usr/jiang定界符所界定内容加工处理 ( 等同双引号处理 ):
变量替换,命令替换不执行文件名生成
第 6章 第 9页
输出重定向 > filename
将 stdout重定向到文件 filename
2> filename将文件句柄 2 重定向到文件 filename
2>&1将文件句柄 2 重定向到文件句柄 1 指向的文件
sh 允许对除 0 , 1 , 2 外其它文件句柄重定向
第 6章 第 10页
输出重定向 ( 例 1) ls -l > file.list
将命令 ls 标准输出 stdout定向到文件 file.list 中 cc try.c -o try 2> try.bugs
将 cc 命令的 stderr重定向到文件 try.bugs 中 try > try.out 2>try.err try 1> try.out 2>try.err
将 try 程序执行后的 stdout和 stderr分别重定向到不同的文件
第 6章 第 11页
sh 输出重定向 ( 例 2) try >rpt 2>&1
stdout和 stderr均存入文件 rpt0123
...
终端
键盘 0123
...
终端
键盘
rpt
0123
...
终端
键盘
rpt
try 2>&1 >rptstderr定向到终端, stdout重定向到文件
0123
...
终端
键盘 0123
...
终端
键盘 0123
...
终端
键盘
rpt
第 6章 第 12页
输出重定向 ( 例 3: 源程序 ) 缓冲 I/O
#include <stdio.h>char *msg = "Msg from file descripter 5\n";main(){ FILE *f = fdopen(5, "w"); if (f) fprintf(f, "%s", msg);}
原始 I/Ochar *msg = "Msg from file descripter 5\n";main(){ write(5, msg, strlen(msg));}
第 6章 第 13页
输出重定向 ( 例 3: 程序执行 ) 编译和执行
cc msg.c -o msg./msg./msg 5>&1
./msg >rpt 5>&1文件句柄 5 重定向到文件句柄 1 指向的文件012345
...
终端
键盘 012345
...
终端
键盘
rpt
012345
...
终端
键盘
rpt
第 6章 第 14页
管道 ls -l | grep '^d' 前一命令的 stdout作后一命令的 stdin cc try.c -o try 2>&1 | more 前一命令的 stdout+stderr作为下一命令的 stdin
0123
...
终端
键盘 0123
...
终端
键盘 0123
...
终端
键盘
PIPE PIPE
6.3 变量
第 6章 第 16页
sh 变量 存储的内容
字符串 ( 对于数字串来说,不是二进制形式 )在执行过程中其内容可以被修改
变量名第一个字符必须为字母其余字符可以是字母,数字,下划线
第 6章 第 17页
变量的赋值和引用 简单赋值与引用
addr=20.1.1.254 echo $addr 注意:赋值作为单独一条命令,等号两侧不许多余空格引用 addr 变量 $addr , echo 执行前, sh 完成变量替
换 赋值时,等号右侧字符串中含有特殊字符,
unit=”Beiyou University”echo $unit
引用未定义变量,变量值为空字符串echo Connect to $proto Networkproto=TCP/IPecho Connect to $proto Network
第 6章 第 18页
命令 echo 语法与功能
echo arg1 arg2 arg3 ...打印各命令行参数,每两个间用一空格分开,最后打印换行符
不可打印字符 ( 转义 ) : Linux 需加选项 -eecho 支持 C 语言字符串常数描述格式的转义和 \c
\c 打印完毕,不换行 \b 退格 \n 换行 \r 回车 \t 水平制表 \\ 反斜线 \nnn 八进制描述的字符 ASCII码
举例echo Beijing Chinaecho "Beijing China"echo '\065' 打印 5echo \\101 打印 Aecho "\r$cnt \c"
第 6章 第 19页
read: 读用户的输入内部命令 read:变量取值的另外一种方
法从标准输入读入一行内容赋值给变量例:读取用户的输入,并使用输入的信息。
$ read name ccp.c$ echo $nameccp.c$ ls -l $name-rw-r--r-- 1 jiang usr 32394 May 27 10:10 ccp.c$
第 6章 第 20页
脚本程序中的行编辑 (1)假设应用程序 myap 运行时从 myap.cfg 中读取配置参数$ cat myap.cfg ID 3098SERVER 192.168.0.251TCP-PORT 3450TIMEOUT 10LOG-FILE /usr/adm/myap.log$ chmod u+x config.ap; cat config.ap #!/bin/shecho 'Input IP address of server computer: \c'read addred myap.cfg > /dev/null <<TOAST/SERVER.diSERVER $addr.wqTOAST
第 6章 第 21页
脚本程序中的行编辑 (2)$ ./config.ap Input IP address of server computer: 202.112.67.213 $ cat myap.cfg ID 3098SERVER 202.112.67.213TCP-PORT 3450TIMEOUT 10LOG-FILE /usr/adm/myap.log$
第 6章 第 22页
环境变量和局部变量 默认类型
所创建的 shell变量,默认为局部变量 内部命令 export
局部变量转换为环境变量,例如:export proto
局部变量和环境变量shell启动的子进程继承环境变量,不继承局部变量子进程对环境变量的修改,不影响父进程中同名变量
相关命令 set/env 内部命令 set 列出当前所有变量及其值
包括环境变量和局部变量外部命令 /bin/env 列出环境变量及其值
第 6章 第 23页
环境变量: shell/C 程序$chmod u+x stat.report; cat stat.reportecho My Computer Connected to $proto Networks$ cc myap.c -o myap; cat myap.cmain(){ char *envstr = getenv(”proto”); printf(”Protocol is %s\n”, envstr ? envstr : “???”);}$proto=TCP/IP$./stat.report 启动一个子进程 shMy Computer Connected to Networks$./myap 启动一个子进程 myapProtocol is ???$export proto$./stat.reportMy Computer Connected to TCP/IP Networks$./myapProtocol is TCP/IP
第 6章 第 24页
系统的环境变量 创建
登录后系统自动创建一些环境变量影响应用程序运行 HOME :用户主目录的路径名 PATH :命令查找路径
与 DOS 不同的是 , 它不首先搜索当前目录PATH=/bin:/usr/bin:/etcPATH=./:/bin:/usr/bin:/etc 先搜索当前目录PATH=/:bin/usr/bin:/etc:./ 后搜索当前目录
PS1 和 PS2 :主提示符和副提示符 sh副提示符:当一个命令在一行内输不完需几行输入一
个命令时,第 2 行及其它行用副提示符 TERM :终端类型
全屏幕操作的软件 ( 如 vi) ,使用它搜索终端库
6.4 替换
第 6章 第 26页
shell 替换 Shell 的替换工作 : 先替换命令行再执行命令
文件名生成变量替换命令替换
文件名生成遵循文件通配符规则,按照字典序排列如 : ls *.c 文件名替换后实际执行 ls a.c x.c
变量替换 ls $HOMEecho ”My home is $HOME, Terminal is $TERM”
第 6章 第 27页
shell 替换 : 命令替换 用反撇号
now=`date` 以命令 date 的 stdout 替换 `date`./arg `date`
实际执行./arg Sun Dec 4 14:54:38 Beijing 2006
frames=`expr 5 + 13`echo $frames18count=10count=`expr $Count + 1`echo $count11
第 6章 第 28页
shell 内部变量:位置参数 $0
脚本文件本身的名字 $1
1 号命令行参数 $#
命令行参数的个数 "$*"
”$1 $2 $3 $4 ...” "$@"
”$1” ”$2” ”$3” …
第 6章 第 29页
位置参数使用举例$ cat paramecho $# echo "Usage: $0 arg1 arg2 ..."./arg "$@"./arg "$*"$ ./param Copy Files to $HOME 4Usage: ./param arg1 arg2 ...0:[./arg]1:[Copy] 2:[Files]3:[to]4:[/usr/jiang]0:[./arg]1:[Copy Files to /usr/jiang]
6.5 元字符
第 6章 第 31页
shell 元字符 (1) 空格 , 制表符
命令行参数的分隔符 回车
执行键入的命令 > < |
重定向与管道 ( 还有 ||) ;
用于一行内输入多个命令 ( 还有 ;;) &
后台运行 ( 还有 &&) $
引用 shell 变量
第 6章 第 32页
shell 的元字符 (2) `
反向单引号,用于命令替换 * [] ?
文件通配符 (echo "*" 与 echo * 不同) \
取消后继字符的特殊作用 ( 转义 )若后继字符为非特殊字符,则不起作用
( ) 用于定义 shell 函数 , 以及定义命令表
分隔符( )><|;& 等除了它们自身的特殊含义外还同时起到
分隔符的作用 ( 同空格 )
第 6章 第 33页
转义符 反斜线作转义符,取消其后元字符的特殊作用 如果反斜线加在非元字符前面,反斜线跟没有一样find / -size +100 \( -name core -o -name \*.tmp \) -exec rm -f {} \;ls -l > file\ listvi 2\>\&1echo Unix\ \ \ System\ V 与 echo Unix System Vecho * 与 echo \*echo $HOME 与 echo \$HOMEecho \A 与 echo Aecho Windows Directory is C:\Windows\WORK.DIRecho Windows Directory is C:\\Windows\\WORK.DIR
第 6章 第 34页
元字符:引号 双引号 "
除 $ 和 ` 外特殊字符的特殊含义被取消需要的转义 \“ \$ \` \\echo * 与 echo "*“
单引号 ' 对所括起的任何字符,不作特殊解释echo "My home dir is $HOME"echo 'My home dir is $HOME'
第 6章 第 35页
转义符与引号及反撇号 配对的双引号中
\" 代替双引号自身 \\ 代表反斜线自身 \` 代替反撇号自身 \$ 代表美元符自身其它情况下的反斜线保持原文不变
echo "\A" 与 echo \Aecho "\$" 与 echo \$
配对的反撇号中 \\ 代表反斜线自身 \` 代替反撇号自身其它情况下的反斜线保持原文不变
配对的单引号 \ 代表反斜线自身不许任何转义,不许出现单引号
第 6章 第 36页
转义符使用举例$ echo 'Don'\''t remove Peter'\''s DOS dir "C:\PETER"!'Don't remove Peter's DOS dir "C:\PETER"!$ echo "jiang's \$HOME is \"$HOME\""jiang's $HOME is "/usr/jiang"$ echo "\`\`pipeline'' is commands separated by |"``pipeline'' is commands separated by |
第 6章 第 37页
反撇号内的转义处理 例:给出程序名字,中止系统中正在运行的进程$ ps -e | grep myap 31650 pts/2 0:00 myap$ kill 31650ps -e | awk '/[0-9]:[0-9][0-9] myap$/{printf("%d ",$1)}'ps -e | awk "/[0-9]:[0-9][0-9] $1\$/{printf(\"%d \",\$1)}"kill `ps -e | awk "/[0-9]:[0-9][0-9] $1\\$/{printf(\\"%d \\",\\$1)}"`$ cat kPIDs=`ps -e|awk "/[0-9]:[0-9][0-9] $1\\$/{printf(\\"%d \\",\\$1)}"`echo "kill $PIDs"kill $PIDs$ chmod u+x k$ ps -e | grep myap27248 pts/2 0:00 myap31714 pts/2 0:00 myap36926 pts/2 0:00 myap$ ./k myapkill 27248 31714 36926
6.6 条件判断
第 6章 第 39页
条件 条件判定的依据
判定一条命令是否执行成功,判断方法 : 命令执行的返回码, 0:TRUE( 成功 ) ,非 0:FALSE( 失败 )
$ ls -d xyzxyz$ echo $? $? 上一命令的返回码, shell 自定义变量0$ ls -d xyz1xyz1: not found$ echo $?2
用管道线连接在一起的若干命令的执行返回码为最后一个命令执行的返回码
第 6章 第 40页
最简单的条件判断用 && 或 || 连结两个命令 cmd1 && cmd2
若 cmd1 执行成功 ( 返回码为 0) 则执行 cmd2 ,否则不执行 cmd2
cmd1 || cmd2 cmd1 执行失败 ( 返回码不为 0) 则执行 cmd2 ,否则
不执行 cmd2
$ ls -d xdir >/dev/null && echo FOUNDFOUND若没有目录 ydir
$ ls -d ydir >/dev/null 2>&1 ||echo No ydirNo ydir
第 6章 第 41页
命令 true 与 false /bin/true
返回码总为 0 /bin/false
返回码总不为 0 有的 shell 为了提高效率,将 true 和 false 设置
为内部命令
第 6章 第 42页
命令 test 与 [ 区别
命令 /usr/bin/[ 要求其最后一个命令行参数必须为 ]
除此之外 /usr/bin/[ 与 /usr/bin/test 功能相同 Linux 中 /usr/bin/[ 是一个指向 test 的符号连接
注意:不要将方括号理解成一个词法符号举例
test -r /etc/motd [ -r /etc/motd ]
第 6章 第 43页
命令 test :文件特性检测 文件特性检测
-f 普通文件 -d 目录文件 -r 可读 -w 可写 -x 可执行 -s size>0
例test -r /etc/motd && echo readable[ -r /etc/motd ] && echo readable
第 6章 第 44页
命令 test :字符串和整数 字符串比较
-n str str 串长度大于 0 str1 = str2 str1 与 str2 串相等 str1 != str2 str1 串与 str2 串不等
注意 : 等号和不等号两侧的空格不可少 [ -n "$a" ] || echo a=NULL 注意 :$a 的引号 test $# = 0 && echo "No argument"
整数的比较 -eq = -ne ≠ -gt > -ge ≥ -lt < -le ≤
例 : test `ls | wc -l` -ge 100 && echo "Too many files"
第 6章 第 45页
命令 test :逻辑运算 逻辑运算
! NOT (非) -o OR (或)-a AND (与)
例 : [ ! -d $cmd -a -x $cmd ] && $cmd 注意:必需的空格不可省略
$ cat hereecho ”count=$#”[ $#=2 ] && echo Here$ ./here *.ccount=9Here
第 6章 第 46页
复合命令 复合命令:条件满足时,执行若干个命令
使用 { } 或者 ( )pwd DIR=/usr/bin[ -d $DIR ] && { cd $DIR echo ”Current Directory is `pwd`” echo ”`ls | wc -l` files”} pwd 执行结果/usr/jiangCurrent Directory is /usr/bin400 files /usr/bin
第 6章 第 47页
{ } 与 ( ) {} 与 () 的书写规则
(list) 在子 shell中执行命令表 list{ list;} 在当前 shell中执行命令表 list
注意 : 左花括号后面必须有一个空格 {} 与 () 的区别
如果将上例中的 {} 改成 () ,那么执行结果如下 :/usr/jiangCurrent Directory is /usr/bin400 files /usr/jiang
第 6章 第 48页
复合命令:举例 使用 {} 时,多行并为一行不要漏掉必需的空格
和分号[ -f core ] && { echo "rm core" rm core}
写成一行应当为[ -f core ] && { echo "rm core";rm core;}
第 6章 第 49页
条件结构 if 语法
if condition
then list
elif condition
then list
else list
fi其中 if/then/elif/else/fi 为关键字 ( 内部命令 )当条件判断后需要两个或多个分支时, && 和 || 就
不够了,这时应当用 if 结构
第 6章 第 50页
条件结构 if :举例$ cat errmonitor
LOGFILE=./errlogdate>>$LOGFILEif test -w errfilethen cat errfile>>$LOGFILE rm errfileelse echo ”No error”>>$LOGFILEfithen 行可和 cat 行合并成一行if 行不可以和 then 行直接合并成一行将两行合并 : 分号使得一行内可以输入多条命令
if test -r errfile; then
第 6章 第 51页
case 结构 基于文件名匹配基础上的多条件分支,语法 :
case word in pattern1) list1;; pattern2) list2;; ...esacword 与 pattern 匹配:使用 shell的文件名匹配规则;; 是一个整体,不能在两分号间加空格,也不能用
两个连续的空行代替可以使用竖线表示多个模式word 与多个模式匹配时,执行遇到的第一个命令表
第 6章 第 52页
case 结构:举例 语法
case "$1" inSTART|start) ... ( 一段程序 ) ;;STOP|stop) ... ( 一段程序 ) ;;*) echo "Usage: $0 [start|stop]" ;;esac
注意:本例中 case 句中 $1 要加上双引号
6.7 循环结构
第 6章 第 54页
while 结构 语法
while condition do list
done 例 1
while test -r lockfiledo sleep 5done
下例有错while test -r lockfile do sleep 5done
例 2while [ -r lockfile ]; do sleep 5;done
第 6章 第 55页
命令 expr :表达式计算 功能:求表达式的值
shell本身不提供数学运算和字符串运算的能力,这些运算借助命令 expr 完成
算数运算 + - * / % ()
注意应该转义的地方必须加反斜线转义应该有空格的地方不允许漏掉。
例 1:求变量 a*(b+c)正确的写法为 expr $a \* \( $b + $c \)
第 6章 第 56页
命令 expr :算数运算 ( 例 )if [ $# = 0 ]then echo "Usage: $0 : <number>"else count=$1 while [ $count -gt 0 ] do count=`expr $count - 1` echo -e "\015 $count \c" sleep 1 done fi
第 6章 第 57页
命令 expr :字符串运算 用法 : expr string : pattern
正则表达式 pattern 匹配字符串 string ,打印匹配长度pattern 中用 \( 和 \) 括起一部分,能匹配时打印括号内
能匹配的部分,否则为空字符串举例
expr 123 : "[0-9]*" 结果为 3expr A123 : "[0-9]*" 结果为 0
第 6章 第 58页
命令 expr :字符串运算 ( 例 ) 设 tty 命令输出类似 /dev/tty6 的结果
expr `tty` : "/dev/tty\(.*\)"expr `tty` : /dev/tty\\\(.\*\\\)termno=`expr \`tty\` : /dev/tty\\\\\\(.\\*\\\\\\)`
expr "$unit" : ".*" 返回变量 unit的长度expr `pwd` : '.*/\([^/]*\)$' 截取路径名的最后一个分量
第 6章 第 59页
for 结构 语法 1
for name in word1 word2 ... do listdone
语法 2for name do listdone相当于for name in $1 $2 ... do listdone
第 6章 第 60页
for 结构:程序举例 1 例 1 :一段开机时自动执行的批处理程序
if [ -d /etc/rc.d ]then for cmd in /etc/rc.d/*/* /etc/rc.d/* do [ ! -d $cmd -a -x $cmd ] && $cmd donefi
第 6章 第 61页
for 结构:程序举例 2 例 2 :将命令行参数逆序显示出来
list=” ”for argdo list=”$arg $list”doneecho ”$list”
第 6章 第 62页
break 内部命令 break
循环结构 for/while 中使用,中止循环例 :将命令行参数逆序输出count=$#cmd=echowhile truedo cmd=”$cmd \$$count” count=`expr $count - 1` [ $count -eq 0 ] && breakdoneeval $cmd
内部命令 eval将实参作为 shell 的输入读入,再经过一轮变量替
换 / 文件名生成 / 命令替换后,执行所得的命令
第 6章 第 63页
continue 内部命令 continue
在循环结构 for/while 中使用,提前结束本轮循环例 :将命令行参数逆序输出count=$#cmd=echowhile truedo cmd=”$cmd \$$count” count=`expr $count - 1` [ $count -gt 0 ] && continue eval $cmd exit 0 done
内部命令 exit终止当前 shell进程
6.8 函数
第 6章 第 65页
shell 函数 语法
name() { list;}参数
调用时函数名后附加上 0 到多个参数在函数体内部以 $1 , $2 , ... 或 $* , $@ 方式引
用 返回值
函数体内用 return 使函数有返回码,返回码 0 代表成功,非零表示失败
函数内部可以创建和修改变量,函数返回后其它程序访问
第 6章 第 66页
Shell 的注释 shell 中使用 # 号作注释
# 号出现在一个单词的首部,那么,从 # 号至行尾的所有字符被忽略
第 6章 第 67页
shell 函数举例get_val() # Usage: get_val 变量名 提示 默认值 取值表{ while true do echo -e "$2 [$3] : \c" ; read val [ "$val" = "" ] && val=$3 for i in $4 do [ "$val" = "$i" ] && break 2 done echo "**** Invalid choice $val, must be in $4" done eval "$1=$val" } # main() get_val INTR "Interrupt Number" 10 "5 10 11 12 14" get_val PORT "I/O Base Address" 800 "512 560 800" get_val BAUD "Baud Rate" 9600 "2400 9600 57600" echo "INTR=$INTR PORT=$PORT BAUD=$BAUD"
6.9 shell 开关和位置变量
第 6章 第 69页
shell 内部开关 shell 内部开关
-x 执行命令前打印出 shell 替换后的命令及参数,为区别于正常的 shell 输出,前面冠以 + 号
+x 取消上述设置-u 当引用一个未定义的变量时,产生一个错误+u 当引用一个未定义的变量时,认为是一个空串
第 6章 第 70页
shell 内部开关程序举例 例: cat chmod1
set -xecho "$*"for ido [ -f $i ] && { chmod a+r $i eoho "$i is readable" }done
内部开关的设置方法set -x 也可以省略,使用命令 sh -x chmodl a*交互式 shell 也可以使用 set -x/set +x使用 shell 的 -x 选项对 shell 程序调试非常有用