Bash while循环

时间:2020-01-09 10:37:28  来源:igfitidea点击:

我们如何在Linux或者Unix环境中重复执行某些任务。

例如,如何监视日志文件以查找特定的日志消息,并且一旦出现该消息,脚本就应该退出。

对于系统管理员来说,有许多这样的任务,其中使用这样的循环条件可以挽救生命。

在这个绝对的初学者教程中,我将分享bash环境中while循环的不同用法以及实时shell脚本示例,并将尝试尽可能地基础,这样即使是新手也可以轻松理解该主题。

Bash中的基本while循环语法

while循环的"语法"将根据我们选择的编程语言(例如C,perl,python,go等)而有所不同。

提供的"语法"只能与bash和shell脚本一起使用

while CONDITION
do 
    CONSEQUENT-COMMANDS
done

了解语法

  • CONDITION可以是任何类型的条件,例如使用比较运算符,添加命令检查等。

  • 根据条件,接下来将执行" CONSEQUENT-COMMANDS"

  • CONSEQUENT-COMMANDS应该在循环迭代应完成并且EXIT否则循环将继续运行无限期

  • 一旦CONSEQUENT-COMMANDS完成,脚本将执行完毕并EXIT循环

示例1:与比较运算符一起使用

  • 我们将从基础开始,即使用比较运算符作为" CONDITION"来执行某些任务

  • 在此脚本中,我定义了两个变量" a"和" b"。

  • 我们要运行循环,直到" a"小于或者等于" b"变量为止

  • 为了达到这个目的,我将在每次迭代完成时将a的值添加到1上。

a=5
b=10
while [ $a -le $b ]
do
   echo "Iteration Number: $a"
   ((a++))
done

该脚本的输出

Iteration Number: 5
Iteration Number: 6
Iteration Number: 7
Iteration Number: 8
Iteration Number: 9
Iteration Number: 10

现在我们可以使用a = $((a + 1))来代替((a ++)),它在实时环境中会更有帮助
我们将为每次迭代添加a + 1的值,但是如果我们要求在a的值后添加2或者其他数字,该怎么办,如下例所示:

a=5
b=10
while [ $a -le $b ]
do
   echo "Iteration Number: $a"
   a=$((a+2))
done

该脚本的输出(在本示例中)是循环循环,每次循环完成时将" +2"添加到a变量中

Iteration Number: 5
Iteration Number: 7
Iteration Number: 9

在这个shell脚本示例中,我将"读取"用户对两个变量的输入

read -r -p "Enter first number: " a
read -r -p "Enter second number: " b
while [ $a -le $b ]
do
   echo "Iteration Number: $a"
   a=$((a+1))
done

我们可以在条件部分添加任何类型的比较运算符

示例2:在为true时使用-无限循环

while循环的好事之一,它的优点是无限循环运行直到条件匹配。

这种方法的"最大缺点"是这种脚本可能会耗尽系统资源,因此我们应该使用这样的无限循环只有当我们知道自己在做什么时。

运行无限循环的"语法"为:

while true;
do
   CONSEQUENT-COMMANDS
   [sleep {value}]
done

我们将在本文后面学习" break"和" continue"语句,强烈建议将它们与" while true"这样的无限循环一起使用。

在此shell脚本示例中,我将在屏幕上打印一些内容,然后继续除非最终用户干预并按Ctrl + C终止循环,尽管我还增加了1秒钟的睡眠时间以避免消耗资源。

#!/bin/bash
while true
do
   echo "I will continue to iterate unless you press Ctrl+C"
   echo "On second thought let me also rest of 1 second between every iteration"
   sleep 1s
done

示例3:从文件逐行读取

这是最常用的功能之一,循环将遍历文件的每个"行",我们可以在单个"行"上执行任务

"读取行"的"语法"为:

FILE="/path/to/your/file"
while read line;
do
   CONSEQUENT-COMMANDS
done < $FILE

我希望"语法"足够清晰易懂。

  • 我们可以使用任何变量名代替" FILE"

  • 将使用FILE变量定义的文件内容作为INPUT并使用<$FILE

  • 因此,循环将使用读取行遍历单个行

  • 我们可以将" CONSEQUENT-COMMANDS"替换为执行" inline"活动的函数

这是一个简单的脚本,我们其中打印/etc/hosts文件中的各行,并在每行前添加行号。

#!/bin/bash
FILE="/etc/hosts"
LINE_NO=1
while read line
do
   echo "Line Number $LINE_NO: $line"
   LINE_NO=$(($LINE_NO+1))
done < $FILE

示例:

GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=/dev/mapper/rhel-swap rd.lvm.lv=rhel/root rd.lvm.lv=rhel/swap rhgb quiet biosdevname=0 net.ifnames=0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true

我们将在此Shell脚本中执行"以下任务":

  • 使用文件的绝对路径定义" FILE"变量(可以为变量选择任何名称)

  • 执行任何任务之前先备份文件

  • 通过读取所提供文件的单个"行"来执行迭代

  • 如果找到GRUB_TIMEOUT,则将超时值5替换为10.

  • 如果找到GRUB_CMDLINE_LINUX,则在该行的末尾添加ipv6_disable = 1

  • 每次循环读取一行时,在LINE_NO变量后添加1. 这我们获取循环正在读取的"行号"

  • 我们将使用LINE_NO提供的Linux编号,使用sed来执行文件中的替换。

  • sed对于初学者可能很复杂,因此我们可以忽略该部分,我只想向我们展示一个实际示例,说明如何使用while循环利用内联任务

#!/bin/bash
FILE="/tmp/grub"
# take backup
cp $FILE ${FILE}.bkp
LINE_NO=1
while read line
do
   if [[ $line =~ GRUB_TIMEOUT ]];then
      echo "Found GRUB_TIMEOUT, performing replacement"
      sed -i ${LINE_NO}'s/5/10/g' $FILE
      echo "" # Adding new line for cleaner output
   elif [[ $line =~ GRUB_CMDLINE_LINUX ]];then
      echo "Found GRUB_CMDLINE_LINUX, disabling ipv6"
      sed -i ${LINE_NO}'s/"$/ipv6.disable=1"/' $FILE
   fi
   LINE_NO=$(($LINE_NO+1))
done < $FILE

执行脚本后,以下是我们的更新内容

GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=/dev/mapper/rhel-swap rd.lvm.lv=rhel/root rd.lvm.lv=rhel/swap rhgb quiet biosdevname=0 net.ifnames=0 ipv6.disable=1"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true

因此,用while循环进行内联替换是"成功"的。

示例4:使用break语句

  • 就像我之前说过的,while循环的好处或者坏处之一,它是无限循环运行直到条件匹配的优势

  • 我们之前在for循环中使用了break语句,因此while循环的用法也相同。

  • 一旦满足特定条件,break语句就会退出迭代。

使用break语句运行无限循环的syntax将是

while [CONDITION];
do
   CONSEQUENT-COMMANDS
    if [CONDITION_MATCH]
	then
	   break #Exit the loop and go to SOME_COMMAND
	fi
   [sleep {value}]
done
SOME_COMMAND
  • 确保添加" break"语句以退出"循环",否则循环将继续无限期运行

  • 我还建议在循环中添加sleep,否则迭代将非常快地消耗掉系统资源,至少我从来没有执行过这样的迭代而没有睡眠。

  • 我仅在语法中添加了一个" if"条件,以便更好地理解,也可以在没有if条件的情况下中断循环,这完全取决于需求

  • 但是在大多数情况下,我们最终还是使用if ifif和else条件来使用break语句。

  • 在此脚本中,我将要求用户输入两个数字

  • "循环"将继续运行,直到第一个提供的数字大于或者等于第二个数字

  • 现在我们也可以在while CONDITION中使用比较运算符,即while [$a -ge $b],但是我使用了另一个if else(我希望你不要介意)

#!/bin/bash
echo "I will run this loop until first number is greater than or equal to second number"
echo "But I hope you won't mind if I sleep for 1 second during the iteration?"
echo "" # new line for cleaner output
while true
do
  read -r -p "Enter first number: " a
  read -r -p "Enter second number: " b
  if [ $a -ge $b ];then
     echo "Bingo, first number $a is greater than or equal to $b, exiting.."
     echo "" # new line for cleaner output
     break
  else
     echo "Oops, first number $a is smaller than second number $b, try again.."
     echo "" # new line for cleaner output
     sleep 1s
  fi
done

让我们还举一个"实际例子":

我将为文件中的某个字符串向grep编写脚本,然后"直到脚本找到它将继续运行的字符串为止。

"还要添加5秒钟的睡眠以避免消耗系统资源添加" break"语句一旦在文件中找到字符串,就退出循环

#!/bin/bash
VAR="hynman"
LOGFILE="/tmp/dummy.log"
while true
do
   # Search for $VAR in $LOGFILE. This will return TRUE if found
   egrep -q $VAR $LOGFILE
   # In bash $? is used to check the exit status of last executed command
   # This will return 0 if the command is successful
   if [ $? == 0 ];then
      echo ""
      echo "Finally found $VAR in $LOGFILE"
      break
   else
      echo ""
      echo "Still looking for $VAR in $LOFILE"
      echo "Let me rest for 5 seconds and then I will check again.."
      sleep 5s
   fi
done

为什么不使用exit而不是break。

作为"exit"还可以帮助我摆脱困境吗。

"不完全正确","exitt"将从脚本中出来,而在带有" BREAK"的情况下,我们只会从" LOOP"中出来,而感到困惑。

这是一个重要的区别。

脚本可能仍想执行LOOP之外的许多其他任务,因此,如果我们使用exit,则所有其余任务都将为FAIL

让我为我们展示一个shell脚本示例(with exit):

#!/bin/bash
echo "I will run this loop until first number is greater than or equal to second number"
echo "But I hope you won't mind if I sleep for 1 second during the iteration?"
echo "" # new line for cleaner output
while true
do
  read -r -p "Enter first number: " a
  read -r -p "Enter second number: " b
  if [ $a -ge $b ];then
     echo "Bingo, first number $a is greater than or equal to $b, exiting.."
     echo "" # new line for cleaner output
     exit 0
  else
     echo "Oops, first number $a is smaller than second number $b, try again.."
     echo "" # new line for cleaner output
     sleep 1s
  fi
done

如我们所见,当CONDITION匹配并且没有执行将在while循环之外运行的echo语句时,脚本退出了。

让我们用break statement运行相同的shell脚本。

#!/bin/bash
echo ""
echo "I will run this loop until first number is greater than or equal to second number"
echo "But I hope you won't mind if I sleep for 1 second during the iteration?"
echo "" # new line for cleaner output
while true
do
  read -r -p "Enter first number: " a
  read -r -p "Enter second number: " b
  if [ $a -ge $b ];then
     echo "Bingo, first number $a is greater than or equal to $b, exiting.."
     echo "" # new line for cleaner output
     break
  else
     echo "Oops, first number $a is smaller than second number $b, try again.."
     echo "" # new line for cleaner output
     sleep 1s
  fi
done

这次我们的循环外的echo语句已经执行,因此我们应该知道何时使用exitbreak语句。

示例:使用继续语句

continue语句的用法类似于我们用于for循环的用法。

从当前迭代中出来并允许循环继续而不会退出循环

使用continue语句的syntaxbreak语句相比几乎没有什么不同,特别是我们执行COMMANDS的顺序。

while [ CONDITION ]
do
  CONSEQUENT-COMMANDS
  if [CONDITION_MATCH]
  then
	continue   # Ignore the current iteration and go to next in the loop and skip SOME_COMMAND
  fi
  SOME_COMMAND
done

这是一个简单的脚本,用于了解continue语句的用法

#!/bin/bash
LINE_NO=0
while true
do
   LINE_NO=$((LINE_NO+1))
   if [[ $LINE_NO -eq 2 ]];then
      continue
   elif
      [[ $LINE_NO -eq 5 ]];then
      echo "Line Number: $LINE_NO [exiting]"
      break
   fi
   echo "Line Number: $LINE_NO"
done
  • 我在每次迭代中都将" LINE_NO"的值添加为" 1"

  • 迭代将一直执行到$LINE_NO变量小于或者等于5为止。

  • 在迭代运行过程中,如果" LINE_NO"等于" 2",则"跳过迭代"并通过循环继续"继续"

  • 如果LINE_NO等于5则中断循环

该脚本的输出:

Line Number: 1
Line Number: 3
Line Number: 4
Line Number: 5 [exiting]

示例6:嵌套while循环

我们还可以根据要求在shell脚本中使用"嵌套的while循环"。

我们可以决定在循环中选择条件运算符或者比较运算符。

  • 在这个shell脚本中,我创建了nested while loop

  • 主while循环将继续在"/tmp"下查找文件,并且一旦文件数量达到大于或者等于" 5"

  • 嵌套循环将创建具有增量的文件,并在文件中添加" date + timestamp"

  • 嵌套循环将创建文件,直到LINE_NO变量小于等于5为止。

  • 一旦文件计数大于等于5,主循环将"中断"。

#!/bin/bash
LINE_NO=0
while true
do
   # Run this loop until LINE_NO is less than or equal to 5
   while [ $LINE_NO -le 5 ]
   do
      file="/tmp/file_`date +%F-%H-%M-%S`"
      # Create file is date timestamp
      echo "Creating $file"
      touch $file
      LINE_NO=$(($LINE_NO+1))
      sleep 1
   done
   # Count the number of files under /tmp with name starting with file
   file_count=`ls -l /tmp/file* | wc -l`
   # if number of files is greater than or equal to 5, break the main while loop
   if [ $file_count -ge 5 ];then
      echo "Enough files created"
      break
   fi
   sleep 1
done