Linux-Shell(八、While)

本文详细介绍Bash脚本中的while循环应用,包括基本用法、读取键盘输入、文件数据处理等。通过实例展示了如何利用while循环进行数据统计、文件备份及菜单创建等常见任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 自己抄书用于记忆的,可能会加点自己写的东西

 18.7 while循环

#While循环用于不断执行一系列命令,也可用于从输入文件中读取数据,其格式为:
while 命令
do
  命令1
  命令2
    ...
done

#虽然通常只是用一个命令,但在while和do之间可以放几个命令
#命令通常用作测试条件
#只有当命令的退出状态为0时,do和done之间命令才被执行,如果退出状态不是0,则循环终止
#命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假

18.7.1 简单的while循环

#以下是一个基本的while循环
#测试条件是:如果COUNTER小于5,那么条件返回真
#COUNTER从0开始,每次循环处理时,COUNTER加1

$ pg whilecount
#!/bin/bash
COUNTER=0
while [ $COUNTER -lt 5 ]
do
  COUNTER=`expr $COUNTER + 1`
  echo $COUNTER
done

#运行上述脚本,返回数字1到5,然后终止

18.7.2 使用while循环读键盘输入

#while循环可用于读取键盘信息
#下面的例子中,输入信息被设置为变量FILM,按<Ctrl-D>结束循环

$ pg whileread
#!/bin/bash

echo "type <Ctrl-D> to terminate"
echo -n "enter your most liked film:"
while read FILM
do
  echo "Yeah,great film the $FILM"
done

18.7.3 用while循环从文件中读取数据

#while循环最常用与从一个文件中读取数据,因此编写脚本可以处理这样的信息
#假定要从下面包含雇员名字、从属部门及其ID号的一个文件中读取信息

$ pg names.txt
Louise Conrad : Accounts : ACC8987
Peter James : Payroll : PR489
Fred Terms : Customer : CUS012
James Lenod : Accounts : ACC887
Frank Pavely : Payroll : PR489

#可以用一个变量保存每行数据,当不再有读取数据时条件为真
#while循环使用输入重定向以保证从文件中读取数据
#注意整行数据被设置为单变量$LINE

$ pg whileread
#!/bin/bash

while read LINE
do
  echo $LINE
done < names.txt

18.7.4 使用IFS读文件

#输出时要去除冒号域分割符,可使用变量IFS
#在改变它之前保存IFS的当前设置
#然后在脚本执行完恢复此设置
#使用IFS可以将域分隔符改为冒号而不是空格或tab键
#这里有3个域需要加域分割符,即NAME、DEPT和ID
#为使输出看起来更清晰,对echo命令使用tab键将域分隔得更开一些

$ pg whilereadifs
#!/bin/bash

SAVEDIFS=$IFS
IFS=:
while read NAME DEPT ID
do
  echo -e "$NAME\t $DEPT\t $ID"
done < names.txt
IFS=$SAVEDIFS


#脚本运行之后
$ whilereadifs
Louise Conrad  Accounts  ACC8987
Peter James    Payroll   PR489
Fred Terms     Customer  CUS012
James Lenod    Accounts  ACC887
Frank Pavely   Payroll   PR489

18.7.5 带有测试条件的文件处理

#大部分while循环里都带有一些测试语句,以决定下一步的动作
#这里从人员文件中读取数据,打印所有细节到一个保留文件中,直至发现James Lenod脚本退出
#测试前反馈的信息要确保“James Lenod”加入保留文件中
#所有变量在脚本顶端被设置完毕
#这样当不得不对变量进行改动时可以节省时间和输入
#所有编辑都放在脚本顶端,而不是混于整个脚本间

$ pg whileread_file
#!/bin/bash

SAVEDIFS=$IFS
IFS=:
HOLD_FILE=hold_file
NAME_MATCH="James Lenod"
INPUT_FILE=names.txt

>$HOLD_FILE
while read NAME DEPT ID
do
  echo  $NAME $DEPT $ID >> $HOLD_FILE
  if [ "$NAME" = "$NAME_MATCH" ];then
    echo "all entries up to and including $NAME_MATCH are in $HOLD_FILE"
    exit 0
  fi
done < $INPUT_FILE
IFS=$SAVEDIFS

#还可以采取进一步动作,列出多少个雇员属于同一部门
#假定每个域都有一个变量名,然后在case语句里用expr增加每行匹配脚本
#任何发现的未知部门知识反馈到标准错误中,如果一个无效部门出现,没有必要退出

$ pg whileread_cond
#!/bin/bash

ACC_LOOP=0; CUS_LOOP=0; PAY_LOOP=0;

SAVEDIFS=$IFS
IFS=:
while read NAME DEPT ID
do
  case $DEPT in
  Accounts) ACC_LOOP=`expr $ACC_LOOP + 1`
    ACC="Accounts"
    ;;
  Customer) CUS_LOOP=`expr $CUS_LOOP + 1`
    CUS="Customer"
    ;;
  Payroll) PAY_LOOP=`expr $PAY_LOOP + 1`
    PAY="Payroll"
    ;;
  *) echo "'basename $0': Unknown department $DEPT" >&2
    ;;
  esac
done < names.txt
IFS=$SAVEDIFS
echo "there are $ACC_LOOP employees assigned to $ACC dept"
echo "there are $CUS_LOOP employees assigned to $CUS dept"
echo "there are $PAY_LOOP employees assigned to $PAY dept"

18.7.6 扫描文件行来进行数目统计

#一个常用的任务是读一个文件,统计包含某些数值列的数值总和
#下面的文件包含有部门STAT和GIFT所卖的商品数量
$ pg total.txt
STAT   3444
GIFT    233
GIFT    252
GIFT    932
STAT    212
STAT    923
GIFT    129

#现在的任务是要统计部门GIFT所卖的各种商品数量
#使用expr保存统计和,看下面的expr语句
#变量LOOP和TOTAL首先在循环外初始化为0
#循环开始后,ITEMS加入TOTAL,第一次循环只包含第一种商品,但随着过程继续,ITEMS逐渐加入TOTAL
#下面的expr语句不断增加计数
LOOP=0
TOTAL=0
...
while...
TOTAL=`expr $TOTAL + $ITEMS`
ITEMS=`expr $ITEMS + 1`
done
#使用expr语句时容易犯的一个错误是开始忘记初始化变量
LOOP=0
TOTAL=0
#如果真的忘了初始化,屏幕上将布满expr错误
#如果愿意,可以在循环内初始化循环变量
TOTAL=`expr ${TOTAL:=0} + ${ITEMS}`
#上面一行如果变量TOTAL未赋值,将其初始化为0
#这是在expr里初始化变量的第一个例子
#另外在循环外要打印出最后总数

$ pg $total
#!/bin/bash

LOOP=0
TOTAL=0
COUNT=0

echo "Items Dept"
echo "------------"
while read DEPT ITEMS
do
  COUNT=`expr $COUNT + 1`
  if [ "$DEPT" = "GIFT" ];then
    TOTAL=`expr $TOTAL + $ITEMS`
    ITEMS=`expr $ITEMS + 1`
    echo -e "$ITEMS\t$DEPT"
  fi
done < total.txt
echo "============"
echo $TOTAL
echo "There were $COUNT entries altogether in the file"

#运行脚本,得到:
$ total
Items Dept
------------
234     GIFT
253     GIFT
933     GIFT
130     GIFT
============
1546
There were 7 entries altogether in the file

18.7.7 每次读一对记录

#有时可能希望每次处理两个记录,也许可从记录中进行不同域的比较
#每次读两个记录很容易,就是要在第一个while语句之后将第二个读语句放在其后
#使用这项技术时,不要忘了不断进行检查,因为它实际上读了大量的记录
$ pg record.txt
record 1
record 2
record 3
record 4
record 5
record 6

#每次读两个记录,下面的例子对记录并不做实际测试

$ pg readpair
#!/bin/bash

while read rec1
do
  read rec2
  echo "This is record one of a pair : $rec1"
  echo "This is record one of a pair : $rec2"
  echo "-----------------------------------"
done < record.txt

#首先来检查确实读了很多记录,可以使用wc命令
$ cat record.txt | wc -1


#共有6个记录,观察其输出
$ readpair
This is record one of a pair : record 1
This is record one of a pair : record 2
-----------------------------------
This is record one of a pair : record 3
This is record one of a pair : record 4
-----------------------------------
This is record one of a pair : record 5
This is record one of a pair : record 6
-----------------------------------

18.7.8 忽略#字符

#假定要使用一般的while循环读一个配置文件,可拣选每一行,大部分都是实际操作语句
#有时必须忽略以一定字符开头的行,这时需要用case语句
#因为#是一个特殊字符,最好首先用反斜线屏蔽其特殊意义
#在#符号后放一个星号*,指定*后可包含任意字符

$ pg ignore_hash
#!/bin/bash
INPUT_FILE=config
if [ -s $INPUT_FILE ];then
  while read LINE
  do
    case $LINE in
    \#*);;
    *) echo $LINE
      ;;
    esac
  done < $INPUT_FILE
else
  echo "`basename $0`:Sorry $INPUT_FILE does not exist or is empty"
  exit 1
fi

18.7.10 while循环和文件描述符

#用while循环将数据读入一个文件
#使用文件描述符3和4,下面的脚本进行文件myfile.txt和myfile.bak的备份
#脚本开始测试文件是否存在,如果不存在或没有数据,脚本立即终止
#while循环用到了空命令(:),这是一个死循环,因为null永远返回真
#尝试读至文件结尾将返回错误,那时脚本也终止执行

$ pg copyfile
#!/bin/bash

FILENAME=myfile.txt
FILENAME_BAK=myfile.bak
if [ -s $FILENAME ];then
  exec 4>$FILENAME_BAK
  exec 3<$FILENAME

  while:
  do
    read LINE <&3
    if [ "$?" -ne 0 ];then
      exec 3<&-
      exec 4<&-
      exit
    fi
  echo $LINE>&4
  done
    echo "`basename $0`:Sorry,$FILENAME is not present or is empty" >&2
  fi

18.9 菜单

#创建菜单时,在while循环里null空命令很合适
#while加空命令意即无限循环,这正是一个菜单所具有的特性
#当然除非用户选择退出或是一个有效选项
#创建菜单只需用while循环和case语句捕获用户输入的所有模式
#如果输入无效,则报警,反馈错误信息,然后继续执行循环直到用户完成处理过程,选择退出选项
#菜单界面应该是友好的,不应该让用户去猜做什么,主屏幕也应该带有主机名和日期,并伴随有运行此菜单的用户名
#由于测试原因,所有选项使用的是系统命令


#首先,使用命令替换设置日期,主机名和用户。日期格式为/DD/MM/YYYY
#参数格式为:
$ date +%d/%m/%y
32/05/1999

#对于主机名,使用hostname-s选项只抽取主机名部分
#主机名有时也包括了完全确认的域名

#可以给变量一个更有意义的名字
MYDATE=`date + %d/%m/%Y`
THIS_HOST=`hostname -s`
USER=`whoami`

#对于while循环,只需将空命令直接放在while后,即为无限循环
#格式为:
while:
do
  命令
done

#要注意实际屏幕显示,不要浪费时间使用大量的echo语句或不断地调整它们
#这里使用本地文档,在分界符后面接受输入,直至分界符被再次定位
#格式为:
command << WORD
any input
WORD

#此技术用于菜单屏幕,也将用于帮助屏幕。帮助屏幕不像这样复杂

#用case语句控制用户选择
#case语句应控制所有这些模式,不要忘了将大写与小写模式并列在一起
#因为有时用户会关闭或打开CAPS LOCK键
#因为菜单脚本不断循环,所以应该允许用户退出,如果用户选择Q或q键,脚本应退出,此时脚本带有0值

#如果用户选择无效,应发出警报并带有警告信息
#这里必须使用系统V版本发出警报:
echo "\007 the bell ring"
#用一个简单的echo和读语句锁屏直到用户点击回车键,这样任何信息或命令输出将可视
#也需要清屏,为此可使用tput命令,如果不这样做,使用clear命令也可以

$ pg menu
#!/bin/bash
MYDATE=`date +%d/%m/%Y`
THIS_HOST=`hostname -s`
USER=`whoami`
while:
do
  tput clear
  cat <<MAYDAY

  echo -e -n "\tYour Choice [1,2,3,H,Q] >"
  read CHOICE
    case $CHOICE in
    1) ls
      ;;
    2) vi
      ;;
    3) who
      ;;
    H|h)
    cat <<MAYDAY
    This is the help screen,nothing here yet to help you!
    MAYDAY
      ;;
    Q|q) exit 0
      ;;
    *) echo -e "\t\007unknown user response"
      ;;
    esac
  echo -e -n "\tHit the return key to continue"
  read DUMMY
done

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值