循环
循环
Bash shell 可以一次又一次地重复特定的指令,直到满足特定条件为止。一组重复执行的指令称为循环。Bash 支持 for 循环与 while 循环。每个循环都必须:首先,必须对循环条件中使用的变量进行初始化,然后开始执行循环。在每次迭代的开始进行测试(条件)。循环主体以修改测试(条件)变量值的语句结束。重复执行一个语句块。
for
for var in item1 item2 ... itemN
do
command1
command2
....
...
commandN
done
# The for loop numerical explicit list syntax
for var in list-of-values
do
command1
command2
....
...
commandN
done
# The for loop explicit file list syntax
for var in file1 file2 file3 fileN
do
command1
command2
....
...
commandN
done
# The for loop variable's contents syntax
for var in $fileNames
do
command1
command2
....
...
commandN
done
# The for loop command substitution syntax
for var in $(Linux-command-name)
do
command1
command2
....
...
commandN
done
# The for loop three-expression syntax
for (( EXP1; EXP2; EXP3 ))
do
command1
command2
command3
done
如果我们在列表遍历的时候希望包含下标,可以借助于外部变量:
INDEX=0
for i in $list; do
echo ${INDEX}_$i
let INDEX=${INDEX}+1
done
数据遍历
for i in 1 2 3 4 5
do
echo "Welcome $i times."
done
for car in bmw ford toyota nissan
do
echo "Value of car is: $car"
done
可以使用数字范围设置 for 循环。范围由开始和结束编号指定。for 循环为项目列表中的每个成员执行一系列命令。BASH 中的一个代表性示例如下所示,显示带有 for 循环(multiplication.sh)的乘法表:
n=$1
# make sure command line arguments are passed to the script
if [ $# -eq 0 ]
then
echo "A shell script to print multiplication table."
echo "Usage : $0 number"
exit 1
fi
# Use for loop
for i in {1..10}
do
echo "$n * $i = $(( $i * $n))"
done
命令结果遍历
for command in date pwd df
do
echo
echo "*** The output of $command command >"
#run command
$command
echo
done
# A simple shell script to display a file on screen passed as command line argument
[ $# -eq 0 ] && { echo "Usage: $0 file1 file2 fileN"; exit 1; }
# read all command line arguments via the for loop
for f in $*
do
echo
echo "< $f >"
[ -f $f ] && cat $f || echo "$f not file."
echo "------------------------------------------------"
done
命令替换不过是将 shell 命令输出存储在字符串或变量中。该命令是一个 shell 命令,必须用重音符或 $(..) 括起来。语法如下:
$(command-name)
`command-name`
var=$(command-name)
NOW=$(date)
echo $NOW
echo "Printing file names in /tmp directory:"
for f in $(ls /tmp/*)
do
echo $f
done
文件遍历
# define an array
ArrayName=(~/.config/*.conf)
for var in "${ArrayName[@]}"
do
command1 on $var
command2
....
...
commandN
done
# A shell script to verify user password database
files="/etc/passwd /etc/group /etc/shadow /etc/gshdow"
for f in $files
do
[ -f $f ] && echo "$f file found" || echo "*** Error - $f file missing."
done
嵌入式 for 循环
嵌套 for 循环表示循环内循环。当您想重复一些事情时,它们很有用。例如,创建一个名为 nestedfor.sh 的 shell 脚本:
#!/bin/bash
# A shell script to print each number five times.
for (( i = 1; i <= 5; i++ )) ### Outer for loop ###
do
for (( j = 1 ; j <= 5; j++ )) ### Inner for loop ###
do
echo -n "$i "
done
echo "" #### print the new line ###
done
对于 i 的每个值,内部循环循环 5 次,变量 j 取值从 1 到 5。当 j 的值超过 5 时,内部 for 循环终止,而当 i 的值超过 5 时,外部循环终止。棋盘是棋类中使用的一种棋盘格,由 64 个正方形组成-八行八列,以两种交替的颜色排列。这些颜色称为“黑色”和“白色”。让我们编写一个名为 Chessboard.sh 的 shell 脚本在屏幕上显示一个棋盘:
#!/bin/bash
for (( i = 1; i <= 8; i++ )) ### Outer for loop ###
do
for (( j = 1 ; j <= 8; j++ )) ### Inner for loop ###
do
total=$(( $i + $j)) # total
tmp=$(( $total % 2)) # modulus
# Find out odd and even number and change the color
# alternating colors using odd and even number logic
if [ $tmp -eq 0 ];
then
echo -e -n "\033[47m "
else
echo -e -n "\033[40m "
fi
done
echo "" #### print the new line ###
done
while
while 的语法如下:
while [ condition ]
do
command1
command2
..
....
commandN
done
当条件为真时,将执行 Command1..commandN。要逐行读取文本文件,请使用以下语法:
while IFS= read -r line
do
command1 on $line
command2 on $line
..
....
commandN
done < "/path/to/filename"
# 或者
while IFS= read -r field1 filed2 field3 ... fieldN
do
command1 on $field1
command2 on $field1 and $field3
..
....
commandN on $field1 ... $fieldN
done < "/path/to dir/file name with space"
#!/bin/sh
i=1
while [ $i -le 50 ]
do
userdel -r stud${i}
i=$(($i+1 ))
done
Create a shell script called while.sh:
#!/bin/bash
# set n to 1
n=1
# continue until $n equals 5
while [ $n -le 5 ]
do
echo "Welcome $n times."
n=$(( n+1 )) # increments $n
done
Save and close the file. Run it as follows:
chmod +x while.sh
./while.sh
Sample outputs:
Welcome 1 times.
Welcome 2 times.
Welcome 3 times.
Welcome 4 times.
Welcome 5 times.
The script initializes the variable n to 1, and then increments it by one. The while loop prints out the “Welcome $n times” until it equals 5 and exit the loop.
- Using ((expression)) Format With The While Loop
You can use ((expression)) syntax to test arithmetic evaluation (condition). If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. To replace while loop condition while [ $n -le 5 ] with while (( num <= 10 )) to improve code readability:
#!/bin/bash
n=1
while (( $n <= 5 ))
do
echo "Welcome $n times."
n=$(( n+1 ))
done
- Reading A Text File
You can read a text file using read command and while loop as follows (whilereadfile.sh):
#!/bin/bash
file=/etc/resolv.conf
while IFS= read -r line
do
# echo line is stored in $line
echo $line
done < "$file"
Save and close the file. Run it as follows:
chmod +x whilereadfile.sh
./whilereadfile.sh
Sample outputs:
nameserver 127.0.0.1
nameserver 192.168.1.254
nameserver 4.2.2.1
- Reading A Text File With Separate Fields
You can store above output in two separate fields as follows (whilereadfields.sh):
#!/bin/bash
file=/etc/resolv.conf
# set field separator to a single white space
while IFS=' ' read -r f1 f2
do
echo "field # 1 : $f1 ==> field #2 : $f2"
done < "$file"
Run it as follows:
chmod +x whilereadfields.sh
./whilereadfields.sh
Sample outputs:
field # 1 : nameserver ==> field #2 : 127.0.0.1
field # 1 : nameserver ==> field #2 : 192.168.1.254
field # 1 : nameserver ==> field #2 : 4.2.2.1
Another useful example for reading and phrasing /etc/passwd file using the while loop (readpasswd.sh):
#!/bin/bash
file=/etc/passwd
# set field delimiter to :
# read all 7 fields into 7 vars
while IFS=: read -r user enpass uid gid desc home shell
do
# only display if UID >= 500
[ $uid -ge 500 ] && echo "User $user ($uid) assigned \"$home\" home directory with $shell shell."
done < "$file"
Save and close the file. Run it as follows:
chmod +x readpasswd.sh
./readpasswd.sh
Sample output:
User nobody (65534) assigned "/nonexistent" home directory with /bin/sh shell.
User vivek (1000) assigned "/home/vivek" home directory with /bin/bash shell.
User oracle (1004) assigned "/usr/lib/oracle/xe" home directory with /bin/bash shell.
User simran (1001) assigned "/home/simran" home directory with /bin/bash shell.
User t2 (1002) assigned "/home/t2" home directory with /usr/local/bin/t2.bot shell.
无限循环
您可以使用 : 特殊命令来测试或设置无限循环或无限循环。由于循环的某些固有特性,当永远无法满足条件时,就会发生无限循环。在某些情况下,这是所需的行为。例如,菜单驱动程序通常会继续运行,直到用户选择退出其主菜单(循环)为止。要设置一个无限的 while 循环,请使用:
- true command - do nothing, successfully (always returns exit code 0)
- false command - do nothing, unsuccessfully (always returns exit code 1)
- : command - no effect; the command does nothing (always returns exit code 0)
#!/bin/bash
# Recommend syntax for setting an infinite while loop
while :
do
echo "Do something; hit [CTRL+C] to stop!"
done
while true
do
echo "Do something; hit [CTRL+C] to stop!"
done
while false
do
echo "Do something; hit [CTRL+C] to stop!"
done
以下菜单驱动的程序通常会继续运行,直到用户通过按 4 选项选择退出为止。case 语句用于将值与 $choice
变量进行匹配,它将根据用户的选择采取适当的措施。创建一个名为 menu.sh 的 shell 脚本:
#!/bin/bash
# set an infinite loop
while :
do
clear
# display menu
echo "Server Name - $(hostname)"
echo "-------------------------------"
echo " M A I N - M E N U"
echo "-------------------------------"
echo "1. Display date and time."
echo "2. Display what users are doing."
echo "3. Display network connections."
echo "4. Exit"
# get input from the user
read -p "Enter your choice [ 1 -4 ] " choice
# make decision using case..in..esac
case $choice in
1)
echo "Today is $(date)"
read -p "Press [Enter] key to continue..." readEnterKey
;;
2)
w
read -p "Press [Enter] key to continue..." readEnterKey
;;
3)
netstat -nat
read -p "Press [Enter] key to continue..." readEnterKey
;;
4)
echo "Bye!"
exit 0
;;
*)
echo "Error: Invalid option..."
read -p "Press [Enter] key to continue..." readEnterKey
;;
esac
done
until
只要列表中的项目继续为 true,until 循环将继续运行命令。一旦项目评估为假,则退出循环。语法为:
until [ condition ]
do
command1
command2
...
....
commandN
done
until 直到返回非零状态为止,while 命令执行直到返回零状态。until 循环总是至少执行一次。
#!/bin/bash
i=1
until [ $i -gt 6 ]
do
echo "Welcome $i times."
i=$(( i+1 ))
done
select
Bash Shell 还提供 select Loop,语法为:
select varName in list
do
command1
command2
....
......
commandN
done
结合 select 和 case 语句:
select varName in list
do
case $varName in
pattern1)
command1;;
pattern2)
command2;;
pattern1)
command3;;
*)
echo "Error select option 1..3";;
esac
done
创建一个名为 select.sh 的 shell 脚本:
#!/bin/bash
# Set PS3 prompt
PS3="Enter the space shuttle to get more information : "
# set shuttle list
select shuttle in columbia endeavour challenger discovery atlantis enterprise pathfinder
do
echo "$shuttle selected"
done
另一个 select 循环示例和决策过程与 case..in..esac 语句(selectshuttle.sh)有关:
# The default value for PS3 is set to #?.
# Change it i.e. Set PS3 prompt
PS3="Enter the space shuttle to get quick information : "
# set shuttle list
select shuttle in columbia endeavour challenger discovery atlantis enterprise pathfinder
do
case $shuttle in
columbia)
echo "--------------"
echo "Space Shuttle Columbia was the first spaceworthy space shuttle in NASA's orbital fleet."
echo "--------------"
;;
endeavour)
echo "--------------"
echo "Space Shuttle Endeavour is one of three currently operational orbiters in the Space Shuttle."
echo "--------------"
;;
challenger)
echo "--------------"
echo "Space Shuttle Challenger was NASA's second Space Shuttle orbiter to be put into service."
echo "--------------"
;;
discovery)
echo "--------------"
echo "Discovery became the third operational orbiter, and is now the oldest one in service."
echo "--------------"
;;
atlantis)
echo "--------------"
echo "Atlantis was the fourth operational shuttle built."
echo "--------------"
;;
enterprise)
echo "--------------"
echo "Space Shuttle Enterprise was the first Space Shuttle orbiter."
echo "--------------"
;;
pathfinder)
echo "--------------"
echo "Space Shuttle Orbiter Pathfinder is a Space Shuttle simulator made of steel and wood."
echo "--------------"
;;
*)
echo "Error: Please try again (select 1..7)!"
;;
esac
done
循环控制
break
使用 break 语句从 FOR,WHILE 或 UNTIL 循环中退出,即停止循环执行。
match=$1 # fileName
found=0 # set to 1 if file found in the for loop
# show usage
[ $# -eq 0 ] && { echo "Usage: $0 fileName"; exit 1; }
# Try to find file in /etc
for f in /etc/*
do
if [ $f == "$match" ]
then
echo "$match file found!"
found=1 # file found
break # break the for looop
fi
done
# noop file not found
[ $found -ne 1 ] && echo "$match file not found in /etc directory"
嵌套循环意味着循环内循环。您可以通过添加 break n 语句在嵌套循环中突破一定数量的级别。n 是嵌套级别数。例如,以下代码将分解出第二条 done 语句:
...
for i in something
do
while true
do
cmd1
cmd2
[ condition ] && break 2
done
done
....
..
continue
Continue 语句用于恢复封闭的 FOR,WHILE 或 UNTIL 循环的下一个迭代。
...
..
for i in something
do
[ condition ] && continue
cmd1
cmd2
done
..
...
...
..
while true
do
[ condition1 ] && continue
cmd1
cmd2
[ condition2 ] && break
done
..
...
通过跳过循环中的其余命令,使用 continue 语句可返回循环的顶部。
#!/bin/bash
# A sample mysql backup script
# Must be run as the root user
# Written by Vivek Gite
# Last updated on : 23/Aug/2003
# ---------------------------------
# MySQL Login Info
MUSER="admin" # MySQL user
MHOST="192.168.1.100" # MySQL server ip
MPASS="MySQLServerPassword" # MySQL password
# format dd-mm-yyyy
NOW=$(date +"%d-%m-%Y")
# Backupfile path
BPATH=/backup/mysql/$NOW
# if backup path does not exists, create it
[ ! -d $BPATH ] && mkdir -p $BPATH
# get database name lists
DBS="$(/usr/bin/mysql -u $MUSER -h $MHOST -p$MPASS -Bse 'show databases')"
for db in $DBS
do
# Bakcup file name
FILE="${BPATH}/${db}.gz"
# skip database backup if database name is adserverstats or mint
[ "$db" == "adserverstats" ] && continue
[ "$db" == "mint" ] && continue
# okay lets dump a database backup
/usr/bin/mysqldump -u $MUSER -h $MHOST -p$MPASS $db | /bin/gzip -9 > $FILE
done
Bind9 named.conf Example
#!/bin/bash
# convert all domain names to a lowercase
DOMAINS="$(echo $@|tr '[A-Z]' '[a-z]')"
# Path to named.conf
NAMEDCONF="/var/named/chroot/etc/named.conf"
# Check named.conf for error
NAMEDCHEKCONF="/usr/sbin/named-checkconf -t /var/named/chroot/"
# Display usage and die
if [ $# -eq 0 ]
then
echo "Usage: $0 domain1 domain2 ..."
exit 1
fi
# okay use for loop to process all domain names passed
# as a command line args
for d in $DOMAINS
do
# if domain alrady exits, skip the rest of the loop
grep $d $NAMEDCONF >/dev/null
if [ $? -eq 0 ]
then
echo "$d exits in in $NAMEDCONF, skiping ..."
continue # skip it
fi
# else add domain to named.conf
echo "Adding domain $d to $NAMEDCONF..."
echo "zone \"${d}\" {" >> $NAMEDCONF
echo " type master;" >> $NAMEDCONF
echo " file \"/etc/named/master.${d}\";" >> $NAMEDCONF
echo " allow-transfer { slaveservers; };" >> $NAMEDCONF
echo "};" >> $NAMEDCONF
# Run named configuration file syntax checking tool
$NAMEDCHEKCONF >/dev/null
if [ $? -ne 0 ] # error found?
then
echo "**** Warning: named-checkconf - Cannot reload named due to errors for $d ****"
else
echo "**** Domain $d sucessfully added to $NAMEDCONF ****"
fi
done