使用bash编写Linux shell脚本--复合命令

简介: 来源:http://blog.csdn.net/fox_lht/article/details/5897336 除了最简单的脚本,你很少想要执行每一个命令。执行一组命令或者重复执行一组命令若干次比执行单个命令更加有助。

来源:http://blog.csdn.net/fox_lht/article/details/5897336

除了最简单的脚本,你很少想要执行每一个命令。执行一组命令或者重复执行一组命令若干次比执行单个命令更加有助。复合命令是将命令封装在一组其他命令中。

从可读性来说,封装后的命令使用缩进格式将会使复合命令的代码清晰并便于阅读。管理员曾经抱怨过我的缩进比标准的缩进少了一个空格(我必须使用尺子在屏幕上测量才能确定此事),我认为这不是什么问题,但是他说,当输入 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

img_e00999465d1c2c1b02df587a3ec9c13d.jpg
微信公众号: 猿人谷
如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】
如果您希望与我交流互动,欢迎关注微信公众号
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

目录
相关文章
|
5天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
22 3
|
5天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
17 2
|
5天前
|
安全 网络协议 Linux
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。
本文详细介绍了 Linux 系统中 ping 命令的使用方法和技巧,涵盖基本用法、高级用法、实际应用案例及注意事项。通过掌握 ping 命令,读者可以轻松测试网络连通性、诊断网络问题并提升网络管理能力。
22 3
|
8天前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
33 6
|
8天前
|
监控 Linux 开发者
如何在 Linux 中优雅的使用 head 命令,用来看日志简直溜的不行
`head` 命令是 Linux 系统中一个非常实用的工具,用于快速查看文件的开头部分内容。本文介绍了 `head` 命令的基本用法、高级用法、实际应用案例及注意事项,帮助用户高效处理文件和日志,提升工作效率。
21 7
|
5天前
|
XML JSON 监控
Shell脚本要点和难点以及具体应用和优缺点介绍
Shell脚本在系统管理和自动化任务中扮演着重要角色。尽管存在调试困难、可读性差等问题,但其简洁高效、易于学习和强大的功能使其在许多场景中不可或缺。通过掌握Shell脚本的基本语法、常用命令和函数,并了解其优缺点,开发者可以编写出高效的脚本来完成各种任务,提高工作效率。希望本文能为您在Shell脚本编写和应用中提供有价值的参考和指导。
14 1
|
10天前
|
监控 Linux
Linux常用命令-2
本文继续介绍Linux常用命令,涵盖目录操作、文件操作、系统信息和进程管理等类别。具体包括mkdir、rmdir、cp、mv、rm、touch、whereis、whatis、dmesg、free、date、cal、ps、kill、killall和top等命令的使用方法和常用参数。
39 7
|
9天前
|
监控 Linux Perl
Linux 命令小技巧:显示文件指定行的内容
在 Linux 系统中,处理文本文件是一项常见任务。本文介绍了如何使用 head、tail、sed 和 awk 等命令快速显示文件中的指定行内容,帮助你高效处理文本文件。通过实际应用场景和案例分析,展示了这些命令在代码审查、日志分析和文本处理中的具体用途。同时,还提供了注意事项和技巧,帮助你更好地掌握这些命令。
23 4
|
8天前
|
缓存 网络协议 Linux
Linux ip命令常用操作
Linux的 `ip`命令是一个强大且灵活的网络管理工具,能够执行从基本的网络接口配置到高级的路由和VLAN管理等多种操作。通过熟练掌握这些常用操作,用户可以更加高效地管理和配置Linux系统的网络环境。无论是在日常管理还是故障排除中,`ip`命令都是必不可少的工具。
11 2
|
8天前
|
缓存 运维 监控
【运维必备知识】Linux系统平均负载与top、uptime命令详解
系统平均负载是衡量Linux服务器性能的关键指标之一。通过使用 `top`和 `uptime`命令,可以实时监控系统的负载情况,帮助运维人员及时发现并解决潜在问题。理解这些工具的输出和意义是确保系统稳定运行的基础。希望本文对Linux系统平均负载及相关命令的详细解析能帮助您更好地进行系统运维和性能优化。
26 3