循环

循环

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
上一页