设为首页 收藏本站
查看: 1059|回复: 0

使用Bash编写Linux Shell脚本-7.复合命令

[复制链接]

尚未签到

发表于 2015-4-29 09:50:17 | 显示全部楼层 |阅读模式
  转载自:http://blog.iyunv.com/fox_lht/archive/2010/09/20/5897336.aspx
7.   复合命令
  除了最简单的脚本,你很少想要执行每一个命令。执行一组命令或者重复执行一组命令若干次比执行单个命令更加有助。复合命令是将命令封装在一组其他命令中。
  从可读性来说,封装后的命令使用缩进格式将会使复合命令的代码清晰并便于阅读。管理员曾经抱怨过我的缩进比标准的缩进少了一个空格(我必须使用尺子在屏幕上测量才能确定此事),我认为这不是什么问题,但是他说,当输入 0 时,它的程序会崩溃。
  复合命令总是有两个命令组成。命令的结束符是该命令相反拼写顺序,就像使用括号将命令括住了。例如:神秘莫测的命令 esac 实际上是复合命令 case 的结束符。
命令状态码
  每一个 Linux 命令都返回一个状态码(退出状态),他是一个 0~255 之间的数字,用来表示该命令遇到的问题。如果状态码返回的是 0 ,则表示该命令运行成功,其他的状态码表示某种错误。
  状态码包含在变量“ $? ”中。
$ unzip no_file.zip
unzip:  cannot find no_file.zip, no_file.zip.zip or no_file.zip.ZIP.
$ printf “%d\n” “$?”
9
  unzip 命令找不到要解压的文件,返回的状态码是 9 。
  非官方的 Linux 惯例使用状态码 127 并且比标准的错误代码要小。例如: ls 返回了状态码 9 ,它表示“ bad file number ”。完整的错误代码列在附录 D :“错误代码”中。
  如果命令被信号中断, Bash 返回状态码 128 ,加上信号码。最终,用户的错误码应该大于 191 , Bash 返回的错误码为 63 。信号码列在附录 E :信号。
if test ! -x “$who” ; then
printf “$SCRIPT:$LINENO: the command $who is not available – “\
“ aborting\n “ >&2
exit 192
fi
  一般,大部分 Linux 命令只是简单的返回 1 或 0 ,表示失败还是成功。这也许就是你的脚本所需要的所有信息。特殊的错误信息任然显示在标准输出上。
$ ls po_1473.txt
po_1473.txt
$ printf “%d\n” $?
0
$ ls no_file
no_file not found
$ printf “%d\n” $?
1
  状态码不同于 let 命令返回的真值(第六章讨论过),本节称之为逻辑表达式。在 let 命令中, false 的值是 0 ,这符合计算机语言的习惯,但是状态码是 0 表示成功而不是失败。
$ let “RESULT=1>0”
$ printf “%d %d\n” “$RESULT” $?
1 0
$ test 1 -gt 0
$ printf “%d\n” $?
0
  let 命令分配 1 给 RESULT ,表明 1 大于 0 。 test 命令返回状态码 0 表明命令运行成功。 let 命令返回状态码 0 ,表明 let 命令成功进行比较。
  这些相反的码和习惯可能会导致错误,这些错误很难调试出来。 Bash 有两个内置命令 true 和 false 。这些是返回的状态码,而不是 let 命令的真值。
$ true
$ printf “%d\n” “$?”
0
$ false
$ printf “%d\n” “$?”
1
  true 命令分配一个成功的状态码( 0 )。 fasle 分配一个错误的状态码( 1 )。
  有点混乱吧?
  如果你需要保存逻辑比较的成功状态最好还是使用 test 命令。大部分外壳使用状态码而不是真值。
  在管道中,一次运行几个命令。从管道返回的状态码是最后一个命令的状态码。下面的示例中,显示的是 wc 命令而不是 ls 命令的状态码。
$ ls badfile.txt | wc -l
ls: badfile.txt: No such file or directory
0
$ printf “%d\n” “$?”
0
  虽然 ls 报告了一个错误,管道返回的还是成功的状态码,因为 wc 命令是运行成功的。
  Bash 也定义了一个数组称之为 PIPESTATUS ,它包含了上此运行管道中每一个命令的单独状态。
  $ ls badfile.txt | wc -l
  ls: badfile.txt: No such file or directory
  0
  $ printf “%d %d\n” “${PIPESTATUS[0]}” “${PIPESTATUS[1]}”
  1 0
  $? 是 PIPESTATUS 数组的最后一个值的别名。
  一个命令或管道可以被“!”进行对状态进行取反操作,如果状态时 0 取反则为 1 , 如果大于 0 ,取反则为 0 。
if 命令
  if 命令执行二选一或多选一的操作。
  通常 if 命令和 test 命令一起使用。
NUM_ORDERS=`ls -1 | wc -l`
if [ “$NUM_ORDERS” -lt “$CUTOFF” ] ; then
printf “%s\n” “Too few orders...try running again later”
exit 192
fi
  这个例子是对当前目录中的文件进行统计,如果没有足够的文件数,则显示一则消息,否则就到 fi 命令结束。
  then 命令前分号是必须要有的,虽然它是和 if 一起工作的,但是它仍然是一个单独的命令,所以需要分号进行分割。
  if 命令亦可以有一个 else 命令的分支,它可以在条件失败的时候运行。
NUM_ORDERS=`ls -1 | wc -l`
if [ “$NUM_ORDERS” -lt “$CUTOFF” ] ; then
printf “%s\n” “Too few orders...but will process them anyway”
else
printf “%s\n” “Starting to process the orders”
fi
  if 命令内部可以嵌套 if 命令。
NUM_ORDERS=`ls -1 | wc -l`
if [[ $NUM_ORDERS -lt $TOOFEW ]] ; then
printf “%s\n” “Too few orders...but will process them anyway”
else
if [[ $NUM_ORDERS -gt $TOOMANY ]] ; then
printf “%s\n” “There are many orders.  Processing may take a long time”
else
printf “%s\n” “Starting to process the orders”
fi
fi
  if 不可以交叉嵌套,即:里面的 if 必须完全在外部 if 命令内。
  为了实现多分支, if 命令可以有 elif 分支, elif 命令是 else if 的简写,它可以减少不必要的嵌套。 elif 命令的最后可以在最后加一个 else 命令,他在所有条件都没有中的时候执行。有了这些知识,你可以重写上面的示例:
NUM_ORDERS=`ls -1 | wc -l`
if [ “$NUM_ORDERS” -lt “$TOOFEW” ] ; then
printf “%s\n” “Too few orders...but will process them anyway”
elif [ “$NUM_ORDERS” -gt “$TOOMANY” ] ; then
printf “%s\n” “There are many orders.  Processing may take a long time”
else
printf “%s\n” “Starting to process the orders”
fi
  if 命令也可以不和 test 命令一起使用,它可以根据命令返回的状态码进行执行相关的任务。
if rm “$TEMPFILE” ; then
printf “%s\n” “$SCRIPT:temp file deleted”
else
printf “%s - status code %d\n” \
“ $SCRIPT:$LINENO: unable to delete temp file” $? 2>&
fi
  在 if 命令中嵌入复杂的命令会使脚本语言难读且难以调试。你应该避免这样做。在这个例子中,如果 rm 命令运行失败,则它先显示自己的提示信息,接着显示脚本中的信息。尽管在 if 命令内部也可以声明变量,但是它很难确定那个变量存在,那个不存在。
case 命令
  case 命令进行模板匹配测试,如果值和某个模板匹配,则执行相应的命令。变量逐个进行测试。
  和 elif 命令不同,测试的状态码来自同一个命令, case 测试变量的值。如果测试字符串的值, case 命令比 elif 命令更好。
  每一个 case 分支都必须用一对分号(;;)进行分割。如果没有分号, Bash 会执行下一个分支并报错。
printf “%s -> “ “1 = delete, 2 = archive.  Please choose one”
read REPLY
case “$REPLY” in
1) rm “$TEMPFILE” ;;
2) mv “$TEMPFILE” “$TEMPFILE.old” ;;
*) printf “%s\n” “$REPLY was not one of the choices” ;;
esac
  星号表示所有没有匹配模板的条件所执行的任务。虽然这是可选的,但是好的设计应该有一个这样的写法,即使里面是一个空语句(:)也是好的。
  模板匹配规则遵循 globbing 规则,参考前一张杰的内容。例如:竖条可以分开多个模板。
  case 同其他计算机语言不一样,不会跟着执行。当一个选择了一个条件,则其他 case 不会执行。
while 循环
  有几个命令都可以实现重复执行一组命令。
  while 命令根据测试条件执行封闭在 while 命令中命令组。如果命令失败,则在 while 命令中的命令组不执行。
printf “%s\n” “Enter the names of companies or type control-d”
while read -p “Company ?” COMPANY; do
if test -f “orders_$COMPANY.txt” ; then
printf “%s\n” “There is an order file from this company”
else
printf “%s\n” “There are no order files from this company”
fi
done
  while 命令使用 done 命令结束。不是你也许认为的 elihw 这样的命令。
  使用 true 命令作为测试条件, while 命令会无限循环下去,因为 true 总是返回成功,循环无疑会一直下去。
printf “%s\n” “Enter the names of companies or type quit”
while true ; do
read -p “Company ?” COMPANY
if [ “$COMPANY” = “quit” ] ; then
break
elif test -f “orders_$COMPANY.txt” ; then
printf “%s\n” “There is an order file from this company”
else
printf “%s\n” “There are no order files from this company”
fi
done
  一个 while 循环可以使用 break 命令提前停止。在到达 break 命令后, Bash 会跳出循环并执行循环外的第一条命令。
  break 后面可以跟着一个数字,表示跳出几层循环。例如:
break 2
  跳出 2 层循环。
  和 break 对应的是 continue 命令,它会对后面的命令忽略,从头开始从新循环。 continue 命令后面也可以跟一个数字表示跳到哪一层的循环。
until 循环
  和 while 循环对应的是 until 循环命令, until 循环是直到测试条件成功才停止执行封闭在 until 语句中命令组,其他基本上和 until 命令相同。它相当于 while !。
until test -f “$INVOICE_FILE” ; do
printf “%s\n” “Waiting for the invoice file to arrive...”
sleep 30
done
  将 false 和 until 一起使用可以建立无限循环, break 和 continue 命令同样也可以用于 until 循环命令。
for 循环命令
  标准的伯恩 for in loop 是变量在这儿文件。 for 命令将一系列值分别放入变量中然后执行包含的命令。
for FILE_PREFIX in order invoice purchase_order; do
if test -f “$FILE_PREFIX””_vendor1.txt” ; then
printf “%s\n” “There is a $FILE_PREFIX file from vendor 1...”
fi
done
  如果 in 后面的参数没有,则 for 在外壳脚本中参数中进行循环。
  break 和 continue 命令可以用于 for 循环。
  因为其他外壳的特性, for 循环不是通用的。
嵌入 let 命令((( .. )))
  let 命令判断如果表达式是 0 则返回状态码 1 ,如果表达式不为 0 ,则返回 0 。和 test 命令可以使用一对方括号来表示更容易阅读一样, let 命令也有更容易阅读的表示,使用双括号。
  下面的列表 7.1 示例使用了 for 循环嵌入 let 命令的表达方式:
  列表 7.1

#!/bin/bash
# forloop.sh: Count from 1 to 9
for (( COUNTER=1; COUNTER&2
exit 192
fi
# Generate the report
printf “Report created on %s by %s\n” “`date`” “$LOGNAME”
printf “\n”
printf “%s\n” “$REPORT_NAME”
printf “\n”
printf “%-12s%12s%12s%12s%12s\n” “Product” “Country” “Foreign” “Total” “Average”
printf “%-12s%12s%12s%12s%12s\n” “——” “——” “——” “——” “——”
{ while read PRODUCT CSALES FSALES ; do
let “ITEMS+=1”
LINE_TOTAL=”CSALES+FSALES”
LINE_AVG=”(CSALES+FSALES)/2”
printf “%-12s%12d%12d%12d%12d\n” “$PRODUCT” “$CSALES” “$FSALES” \
“ $LINE_TOTAL” “$LINE_AVG”
done } < $DATA_FILE
# Print report trailer
printf “%-12s%12s%12s%12s%12s\n” “——” “——” “——” “——” “——”
printf “Total number of products: %d\n” “$ITEMS”
printf “\n”
printf “End of report\n”
exit 0

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-61809-1-1.html 上篇帖子: 用 Shell 脚本访问 MySQL 数据库 下篇帖子: Java调用远程Shell脚本(待验证)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表