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

