100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 使用Bash编写Linux Shell脚本-7.复合命令

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

时间:2022-01-30 03:34:33

相关推荐

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

转载自:/fox_lht/archive//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<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 472235

rules 311797

stencils 846 621

转换为

Report created on Thu Aug 22 18:27:07 EDT 2002 by kburtch

Sales Report

ProductCountryForeignTotalAverage

——————————

binders10245761600800

pencils472235707353

rules3117971108554

stencils8466211467733

——————————

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 -iITEMS=0# number of report items

declare -iLINE_TOTAL=0# line totals

declare -iLINE_AVG=0# line average

declarePRODUCT# product name from data file

declare -iCSALES# country sales from data file

declare -iFSALES# 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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。