使用bash编写Linux shell脚本--复合命令
除了最简单的脚本,你很少想要执行每一个命令。执行一组命令或者重复执行一组命令若干次比执行单个命令更加有助。复合命令是将命令封装在一组其他命令中。
从可读性来说,封装后的命令使用缩进格式将会使复合命令的代码清晰并便于阅读。管理员曾经抱怨过我的缩进比标准的缩进少了一个空格(我必须使用尺子在屏幕上测量才能确定此事),我认为这不是什么问题,但是他说,当输入 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<10; COUNTER++ )) ; do
printf “The counter is now %d/n” “$COUNTER”
done
exit 0
当循环开始时,执行双括号中的第一个表达式,每次循环开始执行第三个表达式,并检查第二个表达式,当第二个表达式返回 false ,循环结束。
$ bash forloop.sh
The counter is now 1
The counter is now 2
The counter is now 3
The counter is now 4
The counter is now 5
The counter is now 6
The counter is now 7
The counter is now 8
The counter is now 9
命令组( {..} )
命令可以使用大括号组合到一个组内。
ls -1 | {
while read FILE ; do
echo “$FILE”
done
}
在本实例中, ls 命令的结果成为组命令的输入。
$ test -f orders.txt && { ls -l orders.txt ; rm orders.txt; } /
|| printf “no such file”
如果文件 orders.txt 存在,文件显示出来,接着被删除。否则显示“ no such file ”。在大括号中的命令需要分号进行分割。
命令也可以使用子外壳进行分组,子外壳将在第九章进行讨论。
report.bash :报表格式化
report.bash 是一个用来给销售数字建立报表的脚本程序。销售数字文件有产品名称、本国销售数、外国销售数来组成。例如: report.bash 把下面的报表
binders 1024 576
pencils 472 235
rules 311 797
stencils 846 621
转换为
Report created on Thu Aug 22 18:27:07 EDT 2002 by kburtch
Sales Report
Product Country Foreign Total Average
—— —— —— —— ——
binders 1024 576 1600 800
pencils 472 235 707 353
rules 311 797 1108 554
stencils 846 621 1467 733
—— —— —— —— ——
Total number of products: 4
End of report
列表 7.2 report.bash
!/bin/bash
#
# report.bash: simple report formatter
#
# Ken O. Burtch
# CVS: $Header$
# The report is read from DATA_FILE. It should contain
# the following columns:
#
# Column 1: PRODUCT = Product name
# Column 2: CSALES = Country Sales
# Column 3: FSALES = Foreign Sales
#
# The script will format the data into columns, adding total and
# average sales per item as well as a item count at the end of the
# report.
# Some Linux systems use USER instead of LOGNAME
if [ -z “$LOGNAME” ] ; then # No login name?
declare –rx LOGNAME=”$USER” # probably in USER
fi
shopt -s -o nounset
# Global Declarations
declare -rx SCRIPT=${0##*/} # SCRIPT is the name of this script
declare -rx DATA_FILE=”report.txt” # this is raw data for the report
declare -i ITEMS=0 # number of report items
declare -i LINE_TOTAL=0 # line totals
declare -i LINE_AVG=0 # line average
declare PRODUCT # product name from data file
declare -i CSALES # country sales from data file
declare -i FSALES # foreign sales from data file
declare -rx REPORT_NAME=”Sales Report” # report title
# Sanity Checks
if test ! -r “$DATA_FILE” ; then
printf “$SCRIPT: the report file is missing—aborting/n” >&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
- dedecms自定义表单提交成功后提示信息修改和跳转链接修改
- dede:arclist orderby=weight dedecms列表页文章按权重排序无效问题
- Golang语言社区--Go语言基础第二节变量
- 如何让帝国CMS7.2搜索模板支持动态标签调用
- 数据视觉盛宴—数据可视化实践之美
- 使用Tensorflow对象检测在安卓手机上“寻找”皮卡丘
- 群用户通过微信小程序可以更好地协作了
- RNN入门与实践
- 群分享:关于Markdown,你可能想知道的
- Logistic回归基础篇之梯度上升算法
- IIS下实现帝国CMS搜索页伪静态
- ASP.NET MVC学习笔记06编辑方法和编辑视图
- 第一篇示例博客
- C#历代版本新特性——面试题常用
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Jmeter 常用函数(24)- 详解 __digest
- you-dont-know-websocket
- Cent os 7之KVM虚拟化基础管理
- 常用 Maven 命令介绍
- Linux下diff命令用法详解
- 详解Numpy中的数组拼接、合并操作
- 批量更改图像尺寸到统一大小
- 张东升,我知道是你!如何使用GAN做一个秃头生产器
- Roslyn 在 NuGet 包中放注释 xml 文件的方法
- OpenCV3 图像的加载、修改、显示与保存
- 打卡群刷题总结0727——搜索旋转排序数组 II
- dotnet 的 TaskCompletionSource 的 TrySetResult 是线程安全
- OpenCV 摄像头与视频读取并显示
- OpenCV 边缘检测
- Spring Boot 集成 WebSocket 实现服务端推送消息到客户端