$echo ${fruit[@]} 有空格时使用 只读变量 readonly fruit 删除变量:只读的无法使用该变量删除 unset name unset fruit 局部变量.环境变量.SHELL变量 导出环境变量:将变量放入环境中 PATH=/sbin:/bin ;export PATH 导出多个变量:export PATH HOME UID export name=value export PTH=/sbin:/bin export FMHOME=/usr/frame CLEARHOME=/usr/atria PATH shell变量 PWD UID SHLVL REPLY RANDOM SECONDS IFS PATH HOME 六.替换 文件名替换 ls * 所有文件和目录 ls ch1* ls *doc ls back*doc ls cgi*st*java ls ch??.doc ls cho[0123456789].doc ls cho[0-9].doc ls [a-z]* ls [A-Z]* ls [a-z A-Z]* ls *[a-Z A-Z 0-9] ls [!a]* 不包含a 变量替换 ${parameter:-word}替换缺省值,只当变量未设置时执行替换 PSI=${parameter:-localhost} "$";export PSI; ${parameter:=word}赋予一个缺省值 PSI=${HOST:='uname -n'} "$"; export PSI HOST; ${parameter:?message} 检查是否已定义,否则提示 :${HOME:? "your home directory is undefined"} ${parameter:+word}已设置时将替换的值 echo ${DEBUG:+ "debug is active "} 命令替换 grep `id -un` /ect/passwd 使用后引号`,不是单引号 up=`date; uptime` user=`who | wc -l` 算术替换 $((expression)) s1:$((((5+3*2)-4)/2)) 整数运算,取整 七.引用 用反斜线引用(单个) echo hello "; world echo you owe "$1250 使用单撇号(一串) echo ']?)' echo it"'s friday 使用双撇号:保证了$和后引号`的替换功能 echo "$user owes;[as of(`date+%m/%d`)]" echo "the dos directory is ""windows [url=file://""temp"]""temp"[/url] "" 引用规则和环境 引用忽略了单词边界 echo hel"lo;w"orld 命令中的组合引用 echo the '$user' variable contains this value ">"|$user|" 在单个参数中嵌入空格 echo "name address" mail -s 'meetion tomorrow' fred jane line2 ' 访问包含特殊字符的文件名而引用 rm 'ch1*' 引用正规表达式通配符 grep '[0-9][0-9]*$' report2 report7 引用反斜线开启echo转义序列 echo -e "line 1"nline 2" -e使得shell将echo转义序列解释成特殊字符而非文本字符 cpio是一个存储和恢复文件的命令 cpio -icvdum 'usr2/*' find / -name 'ch*.doc' -print 匹配所有目录 十.流控制 使用test 文件测试 test option file if [-d /home/ranga/bin ]; then PATH="$ PATH:/home/ranga/bin"; fi 目录是否存在 if [-f $home/.bash_aliai]; then $home/.bash_aliai; fi 测试该文件是否有内容 -b 块特殊文件 -c字符特殊文件 -d目录 -e文件存在 -f规则文件 -g是否设置SGID位的值 -h符号连接 -k是否设置"sticky"位的值 -p为已命名管道 -r文件存在且可读 -s文件存在且大于0 -u设置了SUID位的值 -x文件存在且可执行 -o文件存在且被有效用户ID拥有 字符串比较 -z string长度为0 -n string长度不为0 string1=string2 字符串相等 string1!=string2字符串不等 if [-z "$fruit_basket"]; then echo "your fruit basket is empty"; else echo "your fruit basket has following fruit:$fruit_basket" fi if [ "$fruit" = apple ] ; then echo "it is empty" else echo "it is $fruit" fi 数字比较 if [ $? -eq 0 ] ; then echo "successful"; else echo "an error was encountered" fi -eq 等于 -ne不等于 -lt小于 -le小于等于 -gt大于 -ge大于等于 符合表达式 使用操作符:&& || ! ! expr 取反 expr1 -a expr2 都为真则为真 expr1 -o expr2 一个为真则为真 if [ -z "$DTHOME"] && [-d /usr/dt]; then dthmoe=/usr/dt; fi if [ -z "$dthmoe"] -a -d /usr/dt] ; then dthmoe=/usr/dt; fi 否定一个表达式 if [!-d $home/bin]; then mkdir $home/bin; fi case语句 fruit=kiwi case "$fruit" in apple) echo "apple pie is quite tasty";; banana) echo " i like banana";; kiwi) echo "new zealand is famous for kiwi";; esac if ["$fruit"=apple]; then echo "apple pie is quite tasty" elif ["$fruit"=banana]; then echo "i like anana nut bread" elif ["$fruit"=kiwi]; then echo "new zealand is famous for kiwi" fi case "$term" in *term) term=xterm;; network|dialup|unknown|vt[0-9][0-9][0-9]) term=vt100;; esac 十一.循环 while循环 x=0 while [$x -lt 10] do echo $x x=`echo "$x+1"|bc` --bc为X加1 done while循环嵌套 x=0 while ["$x" -lt 10 ]; do y="$x" while [ "$y" -ge 0 ] ; do echo "$y "c" y=`echo "$y-1"|bc` done echo x=`echo "$x+1"|bc` done RESPONSE= while [-z "$RESPONSE" ] ; do echo "enter the name of a directory where your files are -locate:"c" read ESPONSE if [! -d "$RESPONSE" ] ; then echo "ERROR:please enter a directory pathname" RESPONSE= fi done until循环 x=1 while [ ! $x -ge 10] do echo $x x=`echo "$x+a"|bc done x=1 until [$x -ge 10] do echo $x x=`echo "$x+1"|bc done for循环 for i in 0 1 2 3 4 5 7 9 8 do echo $i done for file in $home/.bash* do cp $file ${home}/public_html chmod a+r ${home}/public_html/${file} done select循环 select component in comp1 comp2 comp3 all none do case $component in comp1|comp2|comp3) compconf $component ;; all) compconf comp1 compconf comp2 compcomf comp3 ;; none) break;; *) echo "error:invalid selection,$reply.";; esac done $PS3="please make aselection=>";export PS3 循环控制 while do read CMD case $CMD in [qQ]|[qQ][uU][iI][tT]) break;; *) process $CMD;; esac done for i in 1 2 3 4 5 do mkdir -p /mnt/backup/docs/ch0${i} if [ $? -eq 0] ; then for j in doc c h m pl sh do cp $home/docs/ch0${i}/*.${j} /mnt/backup/docs/ch0${i} if [ $? -ne 0] ; then break 2 ; --从两个循环中跳出 fi done else echo "could not make backup directory" fi done continue命令 只跳出当前迭代而不是整个循环 for file in $files do if [! -f "$file" ] ; then echo "error:$file is not a file" continue fi #process the file done 十二.参数 特殊shell变量 $0 正在被执行命令的名字 $n 与脚本被激活时所带的参数相对应 $#提供给脚本的参数号 $*所有参数都被双引号引住$*=$1$2 $@所有参数都被双引号引住 [email=$@=$1$2]$@=$1$2[/email] $?前一命令执行后的退出状态 $$当前shell的进程号 $!前一个后台命令的进程号 使用$0 #!/bin/sh case $0 in *listtar) targs="-tvf $1";; *maketar) targs="-cvf $1.tar $1";; esac tar $targs usage语句 case $0 in *listtar) targs="-tvf $1";; *maketar) targs="-cvf $1.tar $1";; *) echo "usage:$0[file|directory]" exit 0 ;; esac 处理参数 USAGE="usage:$0 [ -c|-t] [file|directory]" case "$1" in -t) targs="-tvf $2";; -c) targs="-cvf $2.tar $2";; *) echo "$suage" exit 0 ;; esac 使用basename命令 接受一个绝对或相对路径并返回相应的文件或目录名 $ basename /usr/bin/sh usage:"usage:'basename $0' [-c|-t][file|directory]" case `basename $0` in listtar) targs="-tvf $1";; maketar) targs="-cvf $1.tar $1";; esac tar $targs 公用参数处理 usage="usage:`basename $0` [-c|-t][file|directory]" if [ $# -lt 2]; then echo "$usage" exit 1 fi case "$1" in -t) targs="-tvf $2";; -c) targs="-cvf $2.tar $2";; *) echo "$usage" exit 0 ;; esac tar $targs 处理附加文件 case "$1" in -t) targs="-tvf" for i in "$@"; do if [-f "$i"]; then tar $targs "$i"; fi; done ;; -c) targs="-cvf $2.tar $2"; tar $targs ;; *) echo "$usage"; exit 0 ;; esac usage="usage:`basename $0` [-c|-t][files|directoryies]" if [$# -lt 2] ; then echo "$usage"; exit 1; fi case "$1" in -t) shift; targs="-tvf"; for i in "$@" ; do if [-f "4i"] ; then files=`tar $targs "$i" 2>/dev/null` if [$? -eq 0] ; then echo ; echo "$i"; echo "#files" else echo "error:$i not a tar file" fi else echo "error: $i not a file" fi done ;; -c) shift; targs="-cvf"; tar $targs archive.tar "$@" ;; *) echo "$usage" exit 0 ;; $? shell脚本中的选项分析:getopts verbose=false while getopts f:o:v option; do case "$option" in f) infile="$optarg";; o) outfile="$optarg";; v) verbose=true;; "?) echo "$usage"; exit 1 ;; esac done shift `echo "$optind -1"|bc` if [-z "$1" -a -z "$infile" ] ; then echo "error:input file was not specified" exit 1 fi if [-z "$infile"] ; then infile="$1" ; fi shift命令放弃通过减号提供给脚本的参数 if [-f "$infile"]; then uuencode $infile $infile>$outfile; fi 完整脚本: #!/bin/sh usage="usage:`basename $0` [-v][-f][filename][-o][filename]"; verbose=false while getopts f:o:v option ; do case "$option" in f) infile="$optarg";; o) outfile="$optarg";; v) verbose=true;; "?) echo "$usage"; exit 1 ;; esac done shift `echo "$optind -1"|bc` if [-z "$1" -a -z "$infile"]; then echo "error:input file was not specified" exit 1 fi if [-z "$infile"] ; then infile="$1"; fi :${outfile:=${infile}.uu} if [-f "$infile"]; then if [["$verbose"="true"];then echo "uuencoding $infile to $outfile..."c" fi uuencode $infile $infile > $outfile;ret=$? if ["$verbose"="true"] ; then msg="failed" ; if [$ret -eq 0] ; then msg="done"; fi fi fi exit 0 十三.输入/输出 向终端输出stdout echo printf echo eliza,where the devil are my slippers?!? echo you r home directory is $home echo的转义序列 "n 换行符 "t 跳格键tab "c 字符串不带换行符 echo "your fruit basket contains:"n$fruit_basket" echo "name "tuser name"nsriranga"tranga"nsrivathsa"tvathsa" echo "making directories,please wait ..."t"c" for i ${dirs_to_make} ; do mkdir -p $I;done echo "done" printf 格式化序列类型 s 字符串 c字符 d十进制整数 x十六进制 o八进制 e指数型浮点数 f固定浮点数 g压缩浮点数 printf "%32s %s"n" "file name" "file type" #printf "%-32s %s"n" "file name" "file type" for i in *; do printf "%32s" "$i" if [-d "$i"]; then echo "directory" elif [-h "$i"]; then echo "symolic link" elif [-f "$i"]; then echo "file" else echo "unknown" fi; done 输出重定向:将输出重定向到一个文件 > date>now {date;uptime;who;}>mylog 向一个文件增加内容 {date;uptime;who;}>>mylog 向文件和屏幕重定向输出 date|tee now if ["$logging"!="true"] ; then logging="true";export logging; exec $0|tee $logfile fi 输入 输入重定向 [email protected] lpr 本地文档 cat >urls 读取用户输入 read name YN=yes printf "do you want to play a game [$YN]?" read YN :${YN:=yes} case $YN in [yY]|[yY][eE][sS]) exec xblast;; *) echo "maybe later" ;; esac while read line do case $line in *root*) echo $line;; esac done 管道 pipeline 使用一个程序操纵另一个程序的输出 tail -f /var/adm/messages |more tail的标准输出被管道输送到MORE的标准输入中 ps -ael |grep "$uid" |more ps的标准输出被连到grep的标准输入中,而grep的标准输出被连到more的标准输入中 文件描述符 标准输入stdin 0 标准输出stdout 1 标准错误stderr 2 使用exec可将任何文件和文件描述符关联起来 当需要多次将输入输出重定向到一个文件但又不想多次重复文件名时. exec n>file exec n>>file n为文件描述符 输入/输出重定向通用的格式 将stdout 1和stderr 2重定向到不同的独立文件 command 1>file1 2>file2 command >>file1 2>>file2 for file in $files do ln -s $file ./docs>>/tmp/ln.log 2>/dev/null --删除输出的一个特殊文件 done 将stdout和stderr重定向到同一个文件 command >file 2>&1 command >>file 2>&1 rm -rf /tmp/my_dir > /dev/null 2>&1;mkdir /tmp/my_dir rdate -s ntp.nasa.gov>>/var/log/rdate.log 2>&1 --rdate与时间服务器同步时间 向STDOUT打印消息 echo string 1>&2 printf format args 1>&2 echo string>&2 printf format args>&2 if [!-f $file] ; then echo "error:$file is not a file" > &2; fi 是否为文件 重定向两个文件描述符 n>&m if [-f "$1" ]; then i=0 while read line do i=`echo "$i+1" |bc` done if [$# -ge 1] ; then for file in $@ do exec 5 十四.函数 创建和使用函数 name() {list;} cd() {chdir${l:-$home};psi=`pwd`$";export psi;} 函数举例 列出路径 OLDIFS="$IFS" IFS=: for DIR in $PATH; do echo $DIR ; done IFS="$OLDIFS" IFS是shell的内部域分割符internal field separator lspath() { OLDIFS="$IFS" IFS=: for DIR in $PATH; do echo $DIR ; done IFS="$OLDIFS" } lspath|grep "/usr/dt/bin" 制作用户自己的路径 path= for dir in /bin /sbin /usr/bin /usr/sbin /usr/ccs/bin /usr/ucb ; do if [-d "$dir"]; path = "$path:$dir"; fi done export path SetPath() { for _dir in "$@" do if [-d "$_dir"] ; then path = "$path";"$_dir"; fi done export path unset _dilr } 使用变量替换进行检查 SetPath() { path=${path:"/sbin:/bin"}; for _dir in "$@" do if [-d "$_dir"] ; then path = "$path";"$_dir"; fi done export path unset _dilr } 在函数间共享数据 在文件系统间移动 三个在文件系统移动的命令:popd pushd dirs dirs(){ oldifs="ifs" ifs=: for i in $_dir_stack do echo "$i "c" done echo ifs="$lodifs" } 实现pushd pushd() { req="$1"; if [-z "$req"] ; then req=.; fi if [-d "$req"]; then cd "$seq"> /dev/null 2>$1 if [$? -eq 0] ; then _dir_stack="`pwd`:$_dir_stack"; export _dir_stack; dirs else echo "error:connot change to directory $req.">$2 fi else echo "error:$req is not a directory." >$2 fi unset req } 实现popd 帮助函数 _popd_helper() { popd="$1" if [-z "$popd" ] ; then echo "error:the directory stack is empty" >$2 return 1 fi shift if [-n "$1"]; then _dir_stack="$1"; shift; fo i in $@; do _dir_stack="$_dir_stack:#i"; done else _dir_stac= fi if [-d "$pdpd"] ; then cd "$popd" >/dev/null 2> &1 if [$? -ne 0] ;then echo "error:could not cd to $popd" >&2 fi pwd else echo "error:$popd is not a directory ." >&2 fi export _dir_stack unset popd } 打包函数 popd() { oldifs="$ifs" ifs=: _popd_helper $_dir_stack ifs="$oldifs" } 十五.文本过滤器 head tail grep sort uniq tr head命令 head [-n lines] files 没有选项就显示前10行,否则显示前N行 按最近访问时间将清单排序 -ut ls -lut /home/ranga/public_html 检索前5个 ls -lut /home/ranga/public_html|head -5 ps -ef|grep udm7|head -5 tail命令 tail [-n lines] files 没有选项,显示标准输入的后10行 ls -t按上次改动时间排序 ls -lt /var/spool/mail ls -lt /var/spool/mail|tail -5 ps -ef|grep udm7|tail -5 从最老到最新排序 -r ls -lrt /var/spool/mail|tail -5 tail -f file (follow)当程序正在向文件写入时也可以查看该文件 tail -f /var/log/httpd/access_log 使用grep 在文件中找到包含某个特殊单词或词组的行 grep word file grep pipe ch14.doc grep pipe ch14.doc ch15.01.doc 同时查找多文件 grep -i unix ch16.doc 同时匹配大小写 从stdin(标准输入)中读入 who |grep ranga grep -v home /etc/passwd 不包含单词home 所有运行在一个系统上的bash例程 /bin/ps -ef|grep bash /bin/ps -ef|grep bash|grep -v grep 行号 grep -n pipe ch15.doc ch15-01.doc 只列出文件名 grep -l delete * 统计单词的总数 tr命令 将一个集合中的所有字符改变成另一个集合中的字符,也可用于删除字符集 sort命令为输入文件中的行进行分类 uniq命令打印出文件中所有的唯一行,并列出某个特定行重复的次数 tr命令 tr `set1` `set2` 将set1转变成set2 tr `!?":;"["]{}(),."t"n` ` ` 压缩输出空格 tr -s `set1` 遇到字符多次连续出现时,只使用一次这个字符 echo "feed me"|tr -s `e` fed me echo "shell programming"|tr -s `ln` tr `!?":;"["]{}(),."t"n` ` ` sort命令:统计一个单词使用了多少次,要使用sort命令将文件中的单词排序 先将文件变成每个单词占用一行的格式,要把所有空格变成换行符 tr `!?":;"["]{}(),."t"n` ` ` uniq命令 通过sort使用-u选项删除所有重复的单词 uniq统计单词出现的次数使用-c tr `!?":;"["]{}(),."t"n` ` ` 为数字排序 sort按数字值排序代替按字符串排序-n,最大的数字首先先打-r,缺省时,最后打印出最大的数字 tr `!?":;"["]{}(),."t"n` ` ` sort命令关键字从何处开始到何处结束,以列为单位 sort -k start,end files sort -rn -k 2,2 switched.txt sort -rn -k 2 switched.txt tr命令中字符分类的使用 alnum 字符和数字 alpha 字母 blank 白色空格 cntrl 控制字符 digit 数字 graph 可打印字符,不包括空格 lower 小写字母 print 打印字符,包括空格 punct 标点符号 space 水平或垂直空格 upper 大写字母 xdigit 16进制数字 tr `[:classname:]` `set2` 去掉标点符号和空格 tr `[:punct:]` ` ` 十六.使用正规表达式过滤文本 awk sed激活语法: command 'script' filenames 正规表达式 基本构造块包括:普通字符.元字符(通配符) 用于正规表达式的元字符 . 匹配任何除换行符外的单个字符 * 匹配恰处于*字符前的0个或多个所给字符 [chars] 匹配在chars中给出的任一个字符,用 - 指出字符范围,^若为第一个字符,则匹配在char2中未指定的字符 ^匹配一行的开始 $匹配一行的结尾 "将紧随在"字符后的字符作为文字字符处理 匹配字符 /a.c/ 匹配包含a+c,a-c,abc行 /a*c/ 包含0个或有a且以c结尾的字符串 /ch.*doc/ 以ch开头,doc结尾 指定字符集 在正规表达式中指定具体的字符集,使用中括号[ ] /[chars]/ 常用字符集 [a-z]匹配单个小写字母 [A-Z]匹配单个大写字母 [a-z A-Z]匹配单个字母 [0-9]匹配单个数字 [a-z A-Z 0-9]匹配单个字母或数字 [^T]匹配任何在集合中未给出的字符 /cho[0-9]*doc/ 锚定模式anchoring 只匹配单字the ,以there这类单词开头的行不匹配,只需在后面加一个空格 /the / 字符串the在行的开始位置,使用^元字符 /^the/ 使用$元字符将表达式锚定在一行的末尾 /friend$/ /^chapter[1-9]*[0-9]$/ 匹配空格行 /^$/ 转义元字符 /"$[0-9]*".[0-9][0-9]"/[a-z A-Z]*/ 一些有用的正规表达式 空行: /^$/ 整行:/^.*$/ 一个或多个空格:/*/ html或xml标记:/][^>]*>/ 有效的 [url=/[a-z]url:/[a-z[/url] A-Z][a-z A-Z]*:"/"/[a-Z A-Z 0-9][a-z A-z 0-9".]*.*/ 美元数量格式:/"$[0-9]*".[0-9][0-9]/ 使用sed:sed是一个可用作过滤器的流编辑器 sed `script` files script是一个或多个按如下格式写出的命令:/pattern/action 在sed中可以利用的某些动作 p 打出该行 d 删除该行 s/pattern1/pattern2 用pattern2替代pattern1 打印行 打出价格低于1元的清单 sed `/0".[0-9][0-9]$/p` fruit_prices.txt 会出现都打印,符合条件的打印两次,使用-n sed -n `/0".[0-9][0-9]$/p` fruit_prices.txt 删除行 以单字mango或Mango开头的行都删除 sed '/^[Mn]ango/d' fruit_prices.txt 执行替换 /pattern/s/pattern1/pattern2/ pattern2代替pattern1 s/pattern1/pattern2 sed 's/paech/Peach/' fruit_prices.txt 一次只执行一次替换 执行全局替换 s/pattern1/pattern2/g sed 's/eqal/equal/g' nash.txt 重用表达式的值 s命令为我们提供了&操作符,使得pattern2中重用匹配pattern1的字符串 $1 代替 1 sed 's/*[0-9][0-9]*".[0-9][0-9]$/"$&/' fruit_prices.txt 使用多个sed命令 sed -e 'command1' -e 'command2' ....... files sed -e 's/Paech/Peach/' -e 's/'*[0-9][0-9]*".[0-9][0-9]$/"$&/' fruit_prices.txt mu fruit_pieces.txt fruit_pieces.txt.$$ sed -e 's/Paech/Peach/' -e 's/*[0-9][0-9]*".[0-9][0-9]$/"$&/' fruit_prices.txt.$$ > fruit_prices.txt 在管道中使用sed 如果sed没有收到文件清单,那么作用于STDIN,可在管道中使用sed 删除第一个圆括号后的所有字符 $/usr/bin/id | sed 's/(.*$//' 删除该行开始处的字符串uid $/usr/bin/id |sed -e 's/(.*$//' -e 's/^uid=//' 十七.使用awk过滤文本 基本语法: awk 'script' files 显示从一个文件来的所有输入行 awk '{print;}' fruit_prices.txt 域编辑:自动将输入行分割成域,域缺省分割字符是跳格(tab空格)和空格 当输入一行时,awk将已经分析过的域放入变量1作为第一个域.为了访问一个域,使用域操作符$,第一个为$1 awk中,只有当访问一个域变量的值才使用$,访问其他变量的值时,不要求使用$ 打印水果名和数量 awk '{print$1 $3;}' fruit_prices.txt 列之间有空格,使用, awk '{print$, $3;}' fruit_prices.txt 通过使用printf命令代替print将输出格式化 awk '{printf "%-15s %s"n",$1,$3;}' fruit_prices.txt 执行 模式-特定 行为 大于1美元的加*,低于的不变 awk '/*"$[1-9][0-9]*".[0-9][0-9]*/ {print $1,$2,$3,"*";} /*"$0".[0-9][0-9]*/{print ;}' fruit_prices.txt 格式化问题 awk '/*"$[1-9][0-9]*".[0-9][0-9]*/ {print $0,"*";} /*"$0".[0-9][0-9]*/{print ;}' fruit_prices.txt 比较操作符 expression {actions;} = == != value~/pattern/ value匹配pattern则为真 value!~/pattern/ value不匹配pattern则为真 awk '$375 {print $0;} ' fruit_prices.txt 复合表达式 (expr1)&&(expr2) (expr1)||(expr2) 单价高于1美元且数量少于75 awk '($2-/^"$[1-9][0-9]*".[0-9][0-9]$/)&&($3 next命令 awk '$375 {print $0}' fruit_prices.txt awk '$375 {print $0}' fruit_prices.txt 使用STDIN作为输入 /bin/ls -l | awk '$1 !-/total/{printf "%-32s %s"n",$9,$5;}' 使用数字表达式 awk中的数字操作符 + - * / %求余 ^求幂 for i in $@ do if [-f $i] ; then echo $i awk '/^*$/{x=x+1;print x;}' $i else echo "error:$i not a file" > &2 fi done 赋值操作符 x=x+1 x+=1 awk中的赋值操作符 += 加 -=减 *=乘 /=除 %=求余 ^=求幂 特殊模式:begin end awk '/^&$/{x=x+1;print x;}' $i awk ' begin {actions} 读取任何输入前执行 /pattern/ {actions} /pattern/ {actions} end {actions} 退出前执行 ' files for i in $@ do if [-f "$i"]; then echo "$i"c" awk ' /^*$/{x+=1;} end {printf "%s"n",x;} ' "$i" else echo "error:$i not a file" > &2 fi done 内置变量 awk中的内置变量 FILENAME 当前输入文件的文件名,不应改变 NR 输入文件中当前输入行或记录的编号,不应改变 NF 当前行或记录中域的编号,不应改变 OFS 输入域分割符(缺省为空) FS 输入域分割符(缺省为空或TAB键) ORS 输出记录分割符(缺省为换行符) RS 输入记录分割符(缺省为换行符) FILENAME for i in $@ do if [-f "$i"] ; then awk 'begin {printf "%s"t", FILENAME;} /^*$/ {x+=1;} end {ave=100*(x/NR); printf "%s"t%3.1f"n",x,ave;} ' "$i" else echo "error:$i not a file" >&2 fi done 改变输入域分割符 在BEGIN模式配置FS awk指定-F选项 使用SHELL变量动态指定域分割符 awk 'begin {FS=":";} {print $i,$6;}' /etc/passwd awk -F: '{print $1,$6;}' /etc/passwd 允许awk使用shell变量 awk 'script' awkvar1=value awkvar2=value.......files NUMFRUIT="$1" if [-z "$NUMBRUIT"] ;then NUMBRUIT=75 ; fi awk '$3 $./reorder.sh 25 流控制 if语句 awk '{printf "%s"t",$0; if ($2-/"$[1-9][0-9]*".[0-9][0-9]/) {printf "*"; if ($3 while语句 awk 'begin {x=0 ; while (x do语句:执行至少一次 awk 'BEGIN {'BEGIN {x=0 ; do {x+=1; print x; } while (x nawk gawk awk 建议使用nawk gawk for语句 for (initalize_counter; test_counter; increment_counter) { action } awk '{for (x=1;x 十八.各种工具 eval命令 第二次重新处理命令 output=">out.file" eval echo hello $output : 命令 是一个完整的shell命令 ,只返回一个完成代码 0 ,指示命令成功完成 .也可作一个空操作no-op if [-x $cmd] then : else echo error:$cmd is not executable >&2 fi 作为无限循环 while : do echo "enter some input: "c" read INPUT ["$INPUT" = stop ] && break done 命令估计参数值 :${lines:=24}${term:? "term not set"} lines为空或为定义,则被设为24,term为空或未定义,则发出错误信息 type命令:告诉用户一个指定命令的全路径 type command1 command2... type s1 ls mv sleep命令:用于暂停一段时间(给定秒数) sleep n echo -e "a value must be input!"a" sleep 1 echo -ne ""a" "a音频信号 为听到声音信号需要为"a加上-e选项 sleep 1 echo -ne ""a" 用户清单5分钟增加到文件一次 while : do date who sleep 300 done >>logfile find命令 find start-dir options actions find / -name efrioo -print 寻找efrioo并在屏幕上显示他们的全路径 find /reports/1998 -name efrioo -type f -print -exec lp {} "; /reports/1998 开始目录,只在该目录或子目录寻找 绝对路径: find /u1 -name efrioo -print 相对路径: find ./tmp -name efrioo -print 全目录: find / -name efrioo -print 搜索多目录:find dir1 dir2 -name efrioo print -name efrioo 只找文件名,不检查目录部分 find / -name '*efrioo*' -print -type f 只寻找文件类型为f的文件 (f为规则或普通文件,而不是目录,设备文件) find命令可使用的文件类型 f 规则或普通文件 d 目录 b 块特殊设备 c 字符特殊设备raw l 符号链接 p 有名管道 find -mtime 定位最后一次修改的文件或定位在很久一段时间内都未改变过的文件,该参数以天为单位 find / -mtime -5 -print +n只寻找改变日期在n天之前的 n 只寻找改变日期在n天前当天的 -n只寻找改变日期在n天之内的 find / -mtime +90 -print -mtime 找到上次改变时间大于.刚好.或少于n天前 -atime 找到上次访问时间大于.刚好.或少于n天前 -ctime 找到其inode上次改变时间大于,刚好.或少于n天前的文件 inode是磁盘表中一项,包含有关文件属性.大小.最后访问时间等 find -size 基于文件的块大小定位文件 find / -size +2000 -print 所有大小超过2000块的文件的文件名 +n 只寻找文件大小超过n块的文件 n 只寻找文件大小等于0块的文件 -n 只寻找文件大小小于n块的文件 find 组合选项 find / -name efrioo -size +50 -mtime -3 -print 使用-o 指定一个逻辑条件 或 find / "( -size +50 - o -mtime -3 ") -print find 否定选项 ! find /dev !"(-type b -o -type c -o -type d") -print -print 一个动作,向标准输出显示文件的全路径 若在其他选项之前,则后面的被忽略 -exec lp{}"; 一个动作,使用lp命令打印任何匹配规则的文件的硬拷贝,可指定多个动作 -exec使用户指定一个unix命令 find / -name efrioo -exec chmod a+r {}"; find / -name efrioo -exec rm -f {}" 知道文件后执行rm命令删除它们 -f不要求用户确认 xargs命令:从标准输入接收一系列单词并将这些单词提供给一个给定命令做为参数 cat filelist|xargs rm 使用 -n选项,可以指定每次从标准输入取来多少个参数用语构造命令行 cat filelist|xargs -n 20 rm ls |xargs -n 2 echo ----- 指定每行显示的文件数 ls|grep '^acb'|xargs -n 20 rm expr命令:执行简单的整数算术运算 expr操作符:+ - "* / % expr 3"*5 3乘5 expr 19%7 求余mod 5 expr要求参数都要被分割开,参数之间都由一个空格分开 cnt=`expr $cnt+1` 后引号完成命令替换 匹配一个正规表达式 expr $abc :'.*' .*是一个可以指出所有字符的正规表达式 ,变量$abc中的所有字符都被计算内 expr $abc :'[0-9]*' 计算在字符串开始部分的数字的个数 expr abcdef :'.."(..|)..' 每个点都是一个正规表达式通配符,代表给定字符串中的一个字符,括号中两个字符被输出 bc命令:是一个不局限于整数的算术工具 bc进入 quit退出 bc可精确计算任何大小的数字 + - * / % ^ bc可为一个变量赋一个计算出的值 average = `echo "scale=4;$price/$units"|bc` 设置obase=16 输出进制是16进制 ibase=8 输入进制是8进制 远程shell remsh/rsh/rcmd/remote rmesh remote-sys unix-command 向远程系统拷贝整个目录树 find . -print | cpio -ocva | remsh remote_sys "(cd /destdir "; cpio -icdum ") 高级主题 十九.信号处理 shell脚本中的重要信号 SIGHUP 1 检测控制终端的挂起或控制进程的死亡 SIGINT 2 键盘中断 SINGQUIT 3 从键盘退出 SIGKILL 9 杀死信号 SIGALRM 14 报警时钟信号(为计时器使用) SIGTERM 15 终止信号 信号列表 signal.h man 7 signal man -s 5 signal man 5 signal 可以理解的信号列表 kill -l 缺省动作 终止进程 忽略信号 内核转储(创建一个名为core的文件,包含接受到信号时进程的内存镜像) 停止进程 继续一个停止的进程 传递信号 最常用的是在脚本执行时按 control-c或interrupt键 其他传递信号的常用方法:kill -signal pid TERM 缺省条件下,kill向进程ID为pid的进程发送一个TREM或终止信号 kill pid = kill -s SIGTERM pid HUP kill -s sighup 1001 发送挂起信号 kill -1 1001 如果kill命令不能终止一个进程,用户可向进程发送quit或int(interrupt)信号 kill -s SIGQUIT 1001 kill -s SIGINT 1001 信号处理函数 kill -9 1001 信号处理 trap命令:设置或取消收到一个信号时的动作 trap name signals 当信号被接收时,就执行name中列出的命令或shell函数 三种trap的常见用法:清除临时文件.一直忽略信号.只在关键操作期间忽略信号 清除临时文件 trap "rm -f $TMPF; exit 2 " 1 2 3 15 当接收到一个HUP,INT,QUIT,TERM信号时,就删除在$TMPF中存储的文件并返回代码2指出退出非正常,正常退出返回0 cleanup() { if [-f "$outfile" ] ; then printf "cleaning up..."; rm -f "$outfile" 2>/dev/null; echo "done."; fi } trap cleanup 1 2 3 15 维持一个进程活着的脚本在接收不同信号时行为就不同: #!/bin/sh if [$# -lt 1 ] ; then echo "usage: `basename $0` command." exit 0 fi init() { ---负责停止任何正在运行的程序,然后再次启动它 printf "info: initializing..." kill -0 $! 2>/dev/null; if [$? -eq 0] ; then kill $!>/dev/null 2>&1 if [$? -ne 0] ; then echo "error:already running as pid $!.exiting." exit 1 fi fi $PROG & printf "done."n" } cleanup() { ---负责杀死正在运行的程序并退出 kill -9 $!;exit 2; } #main() --搜索程序并等待它结束,再往下查,接收到一个INT,QUIT,TERM信号时退出 trap cleanup 2 3 15 trap init 1 PROG=$1 init while : ; do wait $! $PROG & done 忽略信号 trap '' signals trap : signals 脚本要忽略所有信号,则应该在脚本开头增加 trap '' 1 2 3 15 给出该命令时,脚本将忽略所有信号,一直到它退出 在关键操作期间忽略信号 trap ' ' 1 2 3 15 doimportantstuff --函数 trap 1 2 3 15 建立一个计时器 使用ALARM信号和一个信号处理函数来设置一个计时器 #main() trap alarmhandler 14 settimer 15 $PROG & CHPROCIDS="$CHPROCIDS $!" --用来维护脚本所有的子进程列表 wait $! umsettimer echo "all done" exit 0 为ALARM信号建立一个处理函数 alarmhandler() { echo "got sigalarm,cmd took too loog." killsubprocs exit 14 } 杀死脚本保存在变量CHPROCIDS中的所有子进程 killsubprocs() { kill ${CHPROCIDS:-$!} if [$? -eq 0 ] ; then echo "sub-processes killed"; fi } settimer 使用settimer函数设置计时器 settimer() { def_tout=${1:-10}; if [$def_tout -ne 0]; then sleep $def_tout && kill -s 14 $$ & CHPROCIDS="CHPROCIDS $!" TIMERPROC=$! fi } 计时器是一个后台进程,要用进程ID 来提交子进程列表,还要在变量TIMER PROC中保存计时器的进程ID,便于以后能取消计时器 unsettimer() { kill $TIMERPROC } 二十.调试 启动调试--调用激活(invocation activated)调试方式 执行shell脚本的一个常用方法 script arg1 arg2 ...argn /bin/sh script arg1 arg2 ... argn 通过为shell提供参数来启动一个调试模式: /bin/sh option script arg1 arg2 ... argn 改变脚本的第一行 #!/bin/sh #!/bin/sh option shell脚本的调试选项 -n 读所有的命令,但不执行它们 -v 在读时显示所有的行 -x 在执行时显示所有命令和他们的参数(为shell跟踪选项) 使用set命令,用户可以在shell脚本的任何地方启动或取消调试 使用set启动调试 set option #!/bin/sh set -x if [-z "$1" ] ; then echo "error:insufficient args" exit 1 fi 使用set取消调试 set +option set +x 取消shell跟踪调试模式 所有为一个脚本启动的调试模式都可以用下面命令来取消 set - 为一个函数启动调试 set -x; buggyfunction; set +x 保证了函数的实现不被改动 语法检查 /bin/sh -n script arg1 arg2 ...argn 使用verbose模式 shell提供-v(verbose)调试模式来检查语法错误发生的上下文,脚本的每一行都打印出来 用户只运行-v选项,则脚本的每一行都要执行 用户要检查语法错误,应组合-n和-v选项: /bin/sh -nv script arg1 arg2 ... argn #!/bin/sh failed() { if [$1 -ne 0 ] ; then echo "failed exiting." ; exit 1; fi echo "don." } echo "deleting old backups,please wait ..."c" rm -r backup> /dev/null 2>&1 failed $? echo "make backup(y/n)?"c" read RESPONSE case $RESPONSE in [yY]|[Yy][Ee][Ss]) echo "making backup,please wait..."c" cp -r docs backup failed ;; [nN]|[Nn][Oo] echo "backup skipped";; esac shell跟踪 -x (execution) set -x; ls *.sh; set +x #!/bin/sh failed() { if [$1 -ne 0] ; then echo "failed exiting " ; exit 1; fi echo "done" } yesno() { echo "$1 (y/n)? "c" read RESPONSE case $RESPONSE in [yY]|[Yy][Ee][Ss]) RESPONSE=y ;; [nN]|[Nn][Oo] RESPONSE=n;; *) RESPONSE=n;; esac } yesno "make backup" if [$RESPONSE ="y"] ; then echo "deleting old backups ,please wait..."c" rm -fr backup >/dev/null 2>&1 failed $? echo "making new backups ,please wait..."c" cp -r docs backup failed $? fi 使用shell跟踪找出逻辑缺陷 使用调试陷阱 调试陷阱是在函数或关键代码部分启动shell跟踪的函数 脚本用一个特定的命令行选项或把环境变量置为true(DEBUG=true或TRACE=true) debug() { if ["$debug"="true"] ; then if ["$1"="on" -o "$1"= "ON"]; then set -x else set +x fi fi } 激活调试:debug on 取消调试:debug 或 debug off 应用在程序中:debug on ......... debug off 脚本运行:DEBUG=true /bin/sh ./s1.sh 二十一.使用函数解决问题 创建一个函数库:一个shell函数库一般不包含任何主代码,而只包含函数 从一个库中包含函数 .file #!/bin/sh .$HOME/lib/sh/messages.sh MSG="hello" echo_error $MSG 命名习惯 库命名 $HOME/lib/sh/libTYSP.sh 使用改库:.$HOME/lib/sh/libTYSP.sh ./usr/local/lib/sh/libTYSP.sh 函数命名 print prompt get 有用的函数 printERROR printWARNING printUSAGE promptYESNO promptRESPONSE getSpaceFree getPID getUID 询问一个问题 询问yes或no问题 promptYESNO() { if [$# -lt 1] ; then printERRPR "insufficient arguments" return 1 fi DEF_ARG="" YESNO="" case "$2" in [yY]|[yY][eE][sS]) DEF_ARG=y;; [nN]|[nN][oO]) DEF_ARG=n;; esac while : do printf "$1 (y/n)?" if [-n "$DEF_ARG" ] ; then printf "[$DEF_ARG]" fi read YESNO if [-z "$YESNO"] ; then YESNO="$DEF_ARG" fi case "$YESNO" in [yY]|[yY][eE][sS]) YESNO=y; break;; [nN]|[nN][oO]) YESNO=n; break;; *) YESNO="";; esac done export YESNO unset DEF_ARG return 0 } 函数应用 promptYESNO "make backup" if ["$YESNO" = "y" ] ; then cp -r docs backup fi 提示要求一个回答 promptRESPONSE() { if [$# -lt 1] ; then printERROR "insuficient arguments" return 1 fi RESPONSE ="" DEF_ARG="" while : do printf "$1 ?" if [-n "$DEF_ARG"] ; then printf "[$DEF_ARG]" fi read RESPONSE if [ -n "$RESPONSE"] ; then break elif [-z "$RESPONSE" -a -n "$DEF_ARG" ] ; then response = "$DEF_ARG" break fi done export RESPONSE unset DEF_ARG return 0 } 函数应用: promptRESPONSE "in which idrectory do you want to install" if [! -d "$RESPONSE" ] ; then echo "the directory $RESPONSE does not exist" promptYESNO "create it " "y" if ["$YESON" = "y" ] ; then mkdir "$RESPONSE" else exit fi fi 检查磁盘空间 getSpaceFree getSpaceUsed 计算剩余空间 使用df -k (k指KB)命令 df -k /home/ranga getSpaceFree() { if [$# -lt 1] ; then printERROR "insufficient arguments" return 1 fi df -k "$1" | awk 'NR!=1 {print $4 ; }' if ["`getSpaceFree /usr/local`" -gt 20000] ; then echo "enough space" fi } 计算已用空间 计算一个目录使用了多少磁盘空间du -s 要输出整个目录和它的内容 du -sk du -sk /home/rang/pub getSpaceUsed() { if [$# -lt 1] ; then printERROR "insufficient arguments" return 1 fi if [! -d "$1" ] ; then printERROR "$1 is not a directory" return 1 fi du -sk "$1"|awk '{print $1;}' if ["`getSpaceSpace /home/ranga/pub`" -gt 10000] ; then printWARNING "your're using to much space!" fi } 通过名字获得进程ID getPID() { if [$# -lt 1] ; then printERROR "INSUFFICIENT ARGUMENTS" return 1 fi PSOPTS = "-ef" /bin/ps $PSOPTS |grep "$1" | grep -v grep |awk '{print $2;}' } ps -ef |grep sshd getPID httpd ps -auwx (linux freebsd) 获得一个用户ID id tiptop id getID() { id $1 |sed -e 's/(.*$//' -e 's/^uid=//' if ["`getUID`" -gt 100] ; then --是否大于100 printERROR "you do not have sufficient privileges" exit 1 } getID getID tiptop 完整函数库 echo "error:" [email=$@>&2]$@>&2[/email] echo "warning:" [email=$@>&2]$@>&2[/email] echo "usage:" $@ df -k "$1" | awk 'NR!=1 {print $4;}' du -sk "$1"| awk '{print $1;}' ps -ef |grep "$1" |grep -v grep |awk '{print $2;}' 二十二.使用shell脚本解决问题 移动目录 rm -rf a cp -r b a rm -rfb 使用tar (内容没有压缩,存储了文件权限和文件所包含的分组和拥有者信息) 创建一个tar文件 tar -cpf - source (- 指出创建的tar文件应被写到STDOUT) 从STDIN解开一个tar文件命令 tar -xpf - (- 指出tar文件应从STDIN读入) 最终使用的命令: tar -cpf - source | (cd destination ; tar -xpf -) source,destination都是目录 mudir.sh #!/bin/sh path = /bin:/usr/bin;export path printERROR() { echo "error:$@">&2 ; exit 1; } printusage() { echo "usage:`/bin/basename $@` [email=$@">&2]$@">&2[/email] ; exit 1; } if [$# -lt 2]; then printusage "[src] [dest] " ; fi if [! -d "$1" ] ; then printERROR "the source $1 is not a directory ,or does not exist" fi SRCDIR_PARENT="`/usr/bin/dirname $1`" ARCDIR_CHILD="`/bin/basename $1`" SRCDIR_PARENT="`(cd $SRCDIR_PARENT ; pwd;)`" if [-d "$2"] ; then DESTDIR=`(cd "$2" ; pwd;)` else DESTDIR="`/usr/bin/dirname $2`" NEWNAME="`/BIN/BASENAME $2`" DESTDIR=`(cd $DESTDIR; pwd;)` if [! -d "$DESTDIR"] ; then printERROR "a parent of the destination directory $2 does not exist" fi fi cd "$SRCDIR_PARENT">/dev/null 2>&1 if [$? -ne 0]; then printERROR "could not cd to $SRCDIR_PARENT" fi /bin/tar -cpf - "$SRCDIR_CHILD" |(cd "$DESTDIR"; /bin/tar -xpf -) if [$? -ne 0]; then printERROR "unable to successfully move $1 to $2" fi if [-n "$NEWNAME"] ; then cd "$DESTDIR">/cev/null 2>&1 if [$? -ne 0] ; then printERROR "could not cd to $DESTDIR" fi /bin/mu "$SRCDIR_CHILD" "$NEWNAME" > /dev/null 2>&1 if [$? -ne 0] ; then printERROR "could not rename $1 to $2" fi cd "$SRCDIR_PARENT" >/dev/null 2>&1 if [$? -ne 0] ; then printERROR "could not cd to $SRCDIR_PARENT" fi fi if [-d "$SRCDIR_CHILD"] ; then /bin/rm -r "$SRCDIR_CHILD">/dev/null 2>&1 if [$? -ne 0] ; then printERROR " could not remove $1" fi fi exit 0 示例 ./mvdir.sh /tmp/ch22 /home/ranga/docs/book ls /tmp /home/ranga/docs/book 维护一个地址簿 显示信息 awk -F:'{ printf "name:%s"nemail:%s"naddress:%s"nphone:%s"n"n",$1,$2,$3,$4;}' showperson脚本程序 #!/bin/sh path=/bin:/usr/bin if [$# -lt 1]; then echo "usage: `basename $0` name" exit 1 fi MYADDRESSBOOK = "$HOME/addressbook" if [! -f "$MYADDRESSBOOK"] ; then echo "ERROR: $MYADDRESSBOOK does not exist ,or is not a file " >&2 exit 1 fi grep "$1" "$MYADDRESSBOOK"| awk -F:'{ printf "%-10s %s"n%-10s %s"n%-10s %s"n%-10s %s"n"n",""name:",$1,"email:",$2,"address:",$3,"phone:",$4;}' exit $?应用: ./showperson ranga 增加信息 addperson脚本的完整程序清单 #!/bin/sh path =/bin:/usr/bin MYADDRESSBOOK=$HOME/addressbook NAME="" EMAIL="" ADDR="" PHONE="" remove_colon() { echo "$@" |tr ':' ' ' ; --删除字符 } if [$# -lt 1 ] ; then stty erase '^?' printf "%-10s " "Name:" ; read NAME printf "%-10s " "Email:" ; read EMAIL printf "%-10s " "Address:" ; read ADDR printf "%-10s " "Phone:" ; read PHONE else usage= "`basename $0` [ -n name] [ -e email] [ -a address] [-p phone]" while getopts n:e:a:p:h OPTION do case $OPTION in n) NAME="$OPTARG" ;; e) EAMIL="$OPTARG";; a) ADDR="$OPTARG";; p) PHONE="$OPTARG";; "?|h) echo "usage:$usage" >&2 ; exit 1 ;; esac done fi NAME="`remove_colon $NAME`" EMAIL="`remove_colon $EMAIL`" ADDR="`remove_colon $ADDR`" PHONE="`remove_colon $PHONE`" echo "$NAME:$EMAIL:$ADDR:$PHONE">>"$MYADDRESSBOOK" exit $? 应用: ./addperson ./addperson -n "james" -e [email protected] -a "1701 main street" 删除信息 delperson脚本程序清单 #!/bin/sh .$HOME/lib/sh/libTYSP.sh path=/bin:/usr/bin if [$# -lt 1] ; then printUSAGE "`basename $0` name" exit 1 fi MYADDRESSBOOK="$HOME/addressbook" if [! -f "$MYADDRESSBOOK"] ; then printERROR "$MYADDRESSBOOK does not exists,or is not a file" exit 1 fi TMPF1=/tmp/apupdate.$$ TMPF2=/tmp/abdelets.$$ docleanup() { rm "$TMPF1" "$TMPF1.new" "$TMPF2" 2>/dev/null;} failed() { if ["$1" -ne 0 ] ; then shift printERROR $0 docleanup exit 1 fi } cp "$MYADDRESSBOOK" "$TMPF1" 2>/dev/null failed $? "could not make a backup of the address book" grep "$1" "$TEMPF1" >"$TMPEF2" 2>/dev/null failed $? "no matches found" exec 5 "$TMPF1.new" 2>/dev/null failed $? "unable to update the address book" mv "$TMPF1.new" "$TMPF1" 2>/dev/null failed $? "unable to update the address book" fi done exec 5/dev/null failed $? "unable to update the address book" mv "$TMPF1" "$MYADDRESSBOOK" 2> /dev/null failed $? "unable to update the address book" docleanup exit $? } 二十三.脚本可移植性 判断UNIX版本 BSD berkeley software distribution openbsd netbsd freebsd sunos system v solaris sunos hp-ux BSD SYSTEM V /bin /usr/bin /sbin /usr/sbin /usr/adm /var/adm /usr/mail /var/mail /var/spool/mail /usr/tmp /var/tmp linux的命令和网络都是基于BSD caldera red hat 使用uname -a 打印所有信息 -m 打印当前硬件类型 -n 打印系统主机名 -r 打印操作系统发行版本号 -s 打印操作系统的名字 sun microsystems --solaris(新 system v) sunos(BSD) uname -rs 判断硬件类型 uname -m 判断一个系统的主机名 hostname uname -n 使用函数判断UNIX版本 getosname() { case '`uname -s` in *BSD) echo bsd ;; SunOS) case `uname -r` in 5.*) echo solaris ;; *) echo sunos;; esac ;; Linux) echo linux ;; HP-UX) echo hpux ;; AIX) echo aix ;; *) echo unknown ;; esac } isos() { if [$# -lt 1] ; then echo "ERROR: insufficient arugments " >&2 return 1 fi REQ=`echo $1 |tr '[A-Z]' '[a-z]'` if ["$REQ" = "`getosname`"] ; then return 0 ; fi return 1 } 提高可移植性的技巧 条件执行 执行远程命令 rsh remote shell if SystemIS HPUX ; then RCMD = remsh else RCMD=rsh fi 设置了变量$RCMD后,可执行远程命令 "$RCMD" host command 抽取 getfreespace() if isos HPUX ; then df -b "$DIR" |awk '{print $5;}' else df -k "$DIR" |awk 'NR!=1 {print $4;}' fi getpid() case `getosname` in bsd|sunos|linux) PSOPTS="-auwx" ;; *) PSOPTS="-ef" ;; esac /bin/ps $PSOPTS 2>/dev/null | grep "$1" |grep -v grep |awk '{print $2;}' 二十四.shell编程疑难解答 判断shell是否交互式 test -t tty -s 如何去掉一条命令的输出 command >/dev/null 去掉一条命令的输出和错误输出,使用标准重定向把STDERR重定向到STDOUT command >/dev/null 2>&1 怎样把信息显示到STDERR echo message 1>&2 怎样判断shell能否找到一条特定的命令 whence -v type name >/dev/null 2>&1; if [$? -ne 0]; then list ; fi 怎样判断任务控制是否在shell中可以使用 jobs jsh if type jobs >/dev/null 2>&1 ; then echo "wo have job control" fi 一次一个的考虑一个shell脚本的每个参数 for arg in "$@" do list done 把一个脚本的所有参数赋给另一条命令 command "$@" 在一条sed命令使用一个shell变量的值 sed "/$DEL/d" file1>file2 检查一个变量是否有一个值 if [-z "$VAR"]; then list ; fi 初始化变量 :${VAR:=default} :${VAR:=`default`} 确定一个目录的全路径名 fullpath='(cd dir; pwd)' 确定一个文件的全路径名 CURDIR=`pwd` cd `dirname file` FULLPATH="`pwd`/`basename file`" cd $CURDIR 定位一个特定文件 find dir -name file -print find dir -name "*txt" -print 把find命令和xargs命令组合在一起目录及其子目录包含的每一个文件中查找一个特定字符串: find dir -type f -print | xargs grep "string" find s -type f -print | xargs grep "/bin" 在一个目录中删除匹配一个特定名字的所有文件 LODIFS="$IFS" IFS=' ' ---为了使for循环在每次循环时把FILE置为正确的值,IFS需要被置成换行符 for FILE in `find . -type f -name "*string*" -print` do rm "$FILE" done IFS="$OLDIFS" 把所有*.aaa文件重命名为*.bbb文件 dos: rename *.aaa *.bbb unix: OLDSUFFIX=aaa NEWSUFFIX=bbb for FILE in *."$OLDSUFFIX" do NEWNAME=`echo "$FILE" |sed -e "s/${OLDSUFFIX}"$/$NEWSUFFIX/"` mv "$FILE" "$NEWNAME" done mv -i 更改前提示 把所有aaa*文件重命名为bbb*文件 OLDSUFFIX=aaa NEWSUFFIX=bbb for FILE in "$OLDSUFFIX"* do NEWNAME=`echo "$FILE" |sed -e "s/^${OLDSUFFIX}/$NEWSUFFIX/"` s/替换 mv "$FILE" "$NEWNAME" done 把文件名置为小写字母 for FILE in * do mv -i "$FILE" `echo "$FILE"|tr '[A-Z]' '[a-z]'` 2>/dev/null done 消除文件中的回车 "r回车 "n换行 tr -d '"015' newfile "015八进制表示的回车 命令快速参考 .在当前 !!重复执行前次命令 alias 为命令创建一个短名字 eval使shell重新解释后面的命令 exit n 用状态码n来结束shell脚本 fc 显示或编辑历史列表中的一条命令 fg把一个后台或悬挂任务放到前台 function 此关键字定义了一个使用局部变量的函数 getopts在循环中重复调用一个函数来处理命令行参数 history显示该用户最近运行的命令 integer整数变量 jobs列出后台和悬挂任务 let执行整数运算 newgrp改变用户的基本分组,影响用户在分组中创建的所有新文件和目录 print 与echo相同 pwd打印出现在正在工作的或当前目录 r 再次执行上一条命令 read 等待一行标准输入并把每个单词保存在后面的变量中 readonly标记后面的变量为只读 select提供一个菜单并使用户可以选择 set显示或改变shell选项 shift丢弃$1并把所有参数提前一个位置来代替它 test提供许多选项来检测文件.字符串和数值,通常用[表示 trap指明当接收到一个特定信号时执行的代码 type显示后面命令的路径名或指出它是一个内部命令还是别名 typeset设置变量类型及其初值 ulimit显示或设置最大文件或资源的限制 umask显示或设置一个屏蔽来影响用户创建的新文件或目录的权限 unalias删除一个别名 unset取消后面所跟变量的定义 until一直循环到测试命令为真 wait一直等待到所有后台任务完成 whence类似于type命令 while循环到一个测试命令为真 文件测试 -a file file存在则为真 -b file file是一个块特殊设备则为真 -c是一个字符特殊设备则为真 -d是一个目录则为真 -f是一个普通文件则为真 -g设置了SGID权限位则为真 -G file的组与用户组相匹配则为真 -k设置了粘位则为真 -L是一个符号链则为真 -O运行该命令的用户拥有该file则为真 -p是一个命名管道或fifo则为真 -r是可读的则为真 -s大小大于0则为真 -S是一个socket则为真 -t文件描述符与一个终端相关联则为真 -u设置了SUID权限位则为真 -w是可写的则为真 -x是可执行的则为真 字符串测试 -z string string为空时为真 -n string string大小不为0时为真 整数比较 n1 -eq n2 等于为真 n1 -ne n2 不等于为真 n1 -gt n2 大于为真 n1 -ge n2 大于等于为真 n1 -lt n2 小于为真 n1 -le n2 小于等于为真 !expr expr为假时为真 -a and && and -o or || or 算术表达式 let "variable=integer_expression" 变量赋值 - 一元减(为后面的值取反) !~ 逻辑not,二进制补码 */% 乘 除 求余 + - >>>2)) =8 =小于等于 大于等于 >>= 参数和变量 uservar=value 赋值 $uservar 用uservar的内容替换 ${uservar} 替换为uservar的内容 uservar[index]=value ${uservar[index]} 把一个值替换到命令行 ${uservar ${uservar[@]} 用所有的数组元素替换并把每个用双引号扩起来 korn数组的初始化 set -A uservar value1 value2 ... bash数组的初始化 uservar=(value1 value2 value3...) 内置shell变量 $0 执行的命令或脚本的名字 $n 定位的参数 $# 命令行中所给变元的数目 $* 所有命令行参数的列表 $@ 所有命令行参数的列表每个用双引号括起 $? 最后一条命令的退出状态值 $$ 当前的shell的PID值(进程ID) $! 最后一个后台命令的PID(进程ID)值 直接影响到变量的内置命令 getopts export read readonly unset shell变量 CDPATH 包含以冒号分开的目录列表 HOME 用户的起始目录 IFS 内部域分割符 OPTARG 由getopts处理的最后一个命令行参数 OPTIND 由getopts处理的最后一个命令行参数的索引值 PATH 包含一个冒号隔开的目录列表来搜索不包含任何斜杠的命令 PS1基本shell提示字符串 PS2续行时的从属提示字符串 PWD 当前目录 RANDOM每次调用时返回一个不同的随机数(0-32767) REPLY 通过select命令读入的最后一行输入 SECONDS自从激活shell后所经过的秒数 SHLVL当前激活的shell的数目 UID用户数字ID号 参数替换 ${parameter} 替换parameter的内容 ${parameter:-word} 替换parameter的内容,但如果它是空的或未定义,则替换word,它可能包含一个未加引号的空格 ${parameter:=word}替换parameter的内容,但如果它是空的或未定义,则把word赋给parameter并替换word ${parameter:?message}替换parameter的内容,但如果是空的或未定义,则放弃脚本并给出message做为一个最终错误消息 ${parameter:+word}如果parameter不是空的,则替换word,否则不替换 仅在korn/bash中使用的参数替换 ${#parameter}替换parameter中包含的字符数 ${#array ${parameter#pattern}如果在parameter的开头找到了给出的正规表达式pattern,则删除匹配的字符并替换其余字符. ${parameter##pattern}和上面同,但在parameter开头的最大可能匹配被删除. ${parameter%pattern}和上面同,但删除parameter末端的最小匹配 ${parameter%%pattern}和上面同,但删除parameter末端的最大匹配 在bourne/korn/bash中使用的模式通配符 * 匹配0个或更多的任意字符 ? 精确匹配一个任意字符
[!list] 精确匹配任意一个不在list中的字符 仅在KORN中使用的通配符 ?(pattern1|pattern2...)匹配模式中的任何一个 *(pattern1|pattern2...)匹配模式0次或多次的出现 +(pattern1|pattern2...)匹配模式一次或多次的出现 @(pattern1|pattern2...)仅匹配模式中的一个 !(pattern1|pattern2...)匹配除了模式以外任何字符串 I/O STDIN 标准输入 0 STDOUT标准输出 1 STDERR标准错误 2 cmd >file cmd 1>file STDOUT保存到file cmd >>file cmd 1>>file STDOUT附加到file cmd 2>file STDERR cmd 2>>file cmd cmd1 |cmd2 把cmd1的STDOUT作为cmd2的STDIN cmd |tee file 把unix命令的STDOUT保存到file中,同时把该文本当作STDOUT exec n>file 把文件描述符n的输出重定向到(覆盖)file ,它用在紧接着的unix命令中 exec n>>file 同上,但是附加到file cmd 2>&1 把STDERR重定向到STDOUT当前所指向的地方,把输出和错误保存在一个文件中或把他们一起送到另一条命令时有用 cmd>file 2>&1 STDERR和STDOUT都保存到FILE中 cmd>&2 把STDOUT重定向做STDERR.在ECHO显示一个错误信息时有用 cmd 1>&2 同上 cmd n>&m 把文件描述符n重定向到文件描述符m当前所指向的地方,大于2的n和m可以用来保存一个I/O目标并在以后提取它 常用命令汇总 echo "b backspace "c 抑制跟踪新行 "f 换页 "n 换行 "r 回车 "t Tab "" 反斜杠 "0nm ASCII码为八进制nm的字符 grep -i 忽略大小写 -l 仅显示包含匹配的文件名,而不是匹配的行 -n 显示文件行号和每个匹配的行 -r 与测试相反,意味着忽略包含模式的行 printf printf "text %[-]m.nx" arguments - 左对齐(可选) m 最小域长度 n 字符串的最大长度,浮点数的小数部分位数 x参数类型 s 字符串 c字符值 d十进制整数值 x十六进制值 o八进制值 e指数浮点值 f固定浮点值 g普通浮点值 sort ---显示排序后的行 -b 忽略开头的空格 -d 忽略开头的标点 -f 不分大小写 -n 按开头数字大小排序 -r 以相反顺序排序 +n 排序时忽略前n个域 gerp fgrep egrep sed vi perl awk都允许在模式搜索中使用正规表达式通配符 所有的正规表达式都可以包含下面的通配符 ^pattern 仅匹配以pattern开始的行 pattern$ 仅匹配以pattern结尾的行 . 恰好匹配一个任意字符
[^list] 恰好匹配一个在list中的任意字符 * 匹配前面元素的0次或多次重复 .* 匹配0个或多个任意字符 扩充正规表达式通配符 "{n"} 匹配前面元素的n次重复 "{n,"} 匹配前面元素的n次或更多次重复 "{n,m"}匹配前面元素的至少n次至多m次重复 ? 匹配前面元素的0次或1次出现 +匹配前面元素的1次或更多次出现 |