Bash while循环
我们如何在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
语句已经执行,因此我们应该知道何时使用exit
和break
语句。
示例:使用继续语句
continue
语句的用法类似于我们用于for循环的用法。
从当前迭代中出来并允许循环继续而不会退出循环
使用continue
语句的syntax
与break
语句相比几乎没有什么不同,特别是我们执行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