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

相关文章
|
8月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
877 1
二、Linux文本处理与文件操作核心命令
|
8月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
497 137
|
8月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
1355 58
|
7月前
|
存储 安全 Linux
Linux卡在emergency mode怎么办?xfs_repair 命令轻松解决
Linux虚拟机遇紧急模式?别慌!多因磁盘挂载失败。本文教你通过日志定位问题,用`xfs_repair`等工具修复文件系统,三步快速恢复。掌握查日志、修磁盘、验重启,轻松应对紧急模式,保障系统稳定运行。
1215 2
|
8月前
|
存储 安全 Unix
七、Linux Shell 与脚本基础
别再一遍遍地敲重复的命令了,把它们写进Shell脚本,就能一键搞定。脚本本质上就是个存着一堆命令的文本文件,但要让它“活”起来,有几个关键点:文件开头最好用#!/usr/bin/env bash来指定解释器,并用chmod +x给它执行权限。执行时也有讲究:./script.sh是在一个新“房间”(子Shell)里跑,不影响你;而source script.sh是在当前“房间”里跑,适合用来加载环境变量和配置文件。
699 9
|
8月前
|
存储 Shell Linux
八、Linux Shell 脚本:变量与字符串
Shell脚本里的变量就像一个个贴着标签的“箱子”。装东西(赋值)时,=两边千万不能有空格。用单引号''装进去的东西会原封不动,用双引号""则会让里面的$变量先“变身”再装箱。默认箱子只能在当前“房间”(Shell进程)用,想让隔壁房间(子进程)也能看到,就得给箱子盖个export的“出口”戳。此外,Shell还自带了$?(上条命令的成绩单)和$1(别人递进来的第一个包裹)等许多特殊箱子,非常有用。
704 2
|
8月前
|
Unix Linux 程序员
Linux文本搜索工具grep命令使用指南
以上就是对Linux环境下强大工具 `grep` 的基础到进阶功能介绍。它不仅能够执行简单文字查询任务还能够处理复杂文字处理任务,并且支持强大而灵活地正则表达规范来增加查询精度与效率。无论您是程序员、数据分析师还是系统管理员,在日常工作中熟练运用该命令都将极大提升您处理和分析数据效率。
644 16
|
8月前
|
缓存 监控 Linux
Linux内存问题排查命令详解
Linux服务器卡顿?可能是内存问题。掌握free、vmstat、sar三大命令,快速排查内存使用情况。free查看实时内存,vmstat诊断系统整体性能瓶颈,sar实现长期监控,三者结合,高效定位并解决内存问题。
744 0
Linux内存问题排查命令详解
|
8月前
|
数据采集 监控 Shell
无需Python:Shell脚本如何成为你的自动化爬虫引擎?
Shell脚本利用curl/wget发起请求,结合文本处理工具构建轻量级爬虫,支持并行加速、定时任务、增量抓取及分布式部署。通过随机UA、异常重试等优化提升稳定性,适用于日志监控、价格追踪等场景。相比Python,具备启动快、资源占用低的优势,适合嵌入式或老旧服务器环境,复杂任务可结合Python实现混合编程。
|
9月前
|
Linux 网络安全 开发工具
技术栈:这50条最常用的 Linux 命令你一定要会!
建议多在终端中实践,遇到不懂的命令就用 man 或 --help 了解详情!
1536 0