Shell脚本学习笔记(三)

第三章 更多的结构化命令

13.1 for命令

bash shell提供了for命令,允许你创建一个遍历一系列值的循环。每次迭代都是用其中一个值来执行已定义好的命令。

//for语法
for var in list
do
	commands
done

在每次迭代中,变量var会包含列表中的当前值。第一次迭代会使用列表中的第一个值,第二次迭代使用第二个值,以此类推,直到列表中的所有值都过一遍。

13.1.1 读取列表中的值

#遍历列表中的所有值
$ cat test1
#!/bin/bash
# basic for command
for test in Alabama Alaska Arizona Arkansas California Colorado
do
echo The next state is $test
done

$ ./test1
The next state is Alabama
The next state is Alaska
The next state is Arizona
The next state is Arkansas
The next state is California
The next state is Colorado

13.1.2 读取列表中的复杂值

#带有单引号会使单词分隔失败
$ cat badtest1
#!/bin/bash
# another example of how not to use the for command
for test in I don't know if this'll work
do
echo "word:$test"
done

$ ./badtest1
word:I
word:dont know if thisll
word:work

有两种方法解决此问题:

  • 使用转义字符\来将单引号转义
  • 使用双引号来定义用到单引号的值
$ cat test2
#!/bin/bash
# another example of how not to use the for command
for test in I don\'t know if "this'll" work
do
echo "word:$test"
done
$ ./test2
word:I
word:don't
word:know
word:if
word:this'll
word:work

for命令用空格来划分列表的每个值。如果在单独的数据值中有空格,就必须使用双引号将这些值圈起来。在某个值两边使用双引号时,shell并不会将双引号当成值的一部分。

3.1.3 从变量读取列表

$ cat test4
#!/bin/bash
# using a variable to hold the list
#使用list存储列表
list="Alabama Alaska Arizona Arkansas Colorado"
#在list变量末尾添加变量“Connecticut”
list=$list" Connecticut"
for state in $list
do
echo "Have you ever visited $state?"
done
$ ./test4
Have you ever visited Alabama?
Have you ever visited Alaska?
Have you ever visited Arizona?
Have you ever visited Arkansas?
Have you ever visited Colorado?
Have you ever visited Connecticut?

3.1.4 从命令读取值

$ cat test5
#!/bin/bash
# reading values from a file
file="states"
#使用cat命令来输出文件states的内容。本示例中文件state与脚本test5在同一个文件夹中,如果不是,需要使用全路径名(绝对路径或相对路径)来引用文件位置
for state in $(cat $file)
do
echo "Visit beautiful $state"
done
$ cat states
Alabama
Alaska
Arizona
$ ./test5
Visit beautiful Alabama
Visit beautiful Alaska
Visit beautiful Arizona

3.1.5 更改字段分隔符

换将变量IFS,叫做内部字段分隔符(internal field separator),IFS环境变量定义了bash shell用作字段分隔符的一系列字符。默认情况下,bash shell会将下列字符当做字段分隔符:

  • 空格
  • 制表符
  • 换行符

bash shell在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。

#使用以下语句设定bash shell只识别换行符作为新数据将字段的开始,bash shell会忽略空格和制表符
IFS = $'\n'

在处理代码量较大的脚本时,可能在一个地方需要修改IFS的值,然后忽略这次修改,在脚本的其他地方继续沿用IFS的默认值。可以在改变IFS之间保存原来的IFS值,之后再恢复它。

IFS.OLD = $IFS
IFS = $'\n'
#省略代码块
...
IFS = $IFS.OLD

如果需要指定多个IFS字符,只要将它们在赋值行串起来就行。

#这个赋值会将换行符,冒号,分号和双引号作为字段分隔符
IFS = $'\n':;"

3.1.6 用通配符读取目录

使用for命令来自动遍历目录中的文件。进行此操作时,必须在文件或路径名中使用通配符。他会强制shell使用文件拓展匹配。文件拓展匹配是生成匹配指定通配符的文件名或路径名的过程。

$ cat test6
#!/bin/bash
# iterate through all the files in a directory
for file in /home/rich/test/*
do
#Linux中,目录名和文件名中包含空格是合法的,所以应该使用是双引号将“$file”圈起来
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
$ ./test6
/home/rich/test/dir1 is a directory
/home/rich/test/myprog.c is a file
/home/rich/test/myprog is a file
/home/rich/test/myscript is a file
/home/rich/test/newdir is a directory
3.2 C语言风格的for命令

3.2.1 C语言的for循环

for(i=0; i<10; i++)
{
	printf("The next number is %0d\n", i);
}

3.2.2 bash中C语言风格的for循环

for((variable assignment; condition; iteration process))

bash中C语言风格的循环中,变量的引用不使用$符号,与单独引用变量不同;同时变量迭代的过程不使用expr命令格式。

$ cat test8
#!/bin/bash
# testing the C-style for loop
for (( i=1; i <= 3; i++ ))
do
echo "The next number is $i"
done
$ ./test8
The next number is 1
The next number is 2
The next number is 3

3.2.3 使用多个变量

bash中C语言风格的for循环允许为迭代使用多个变量,循环会单独处理每个变量。

$ cat test9
#!/bin/bash
# multiple variables
for (( a=1, b=10; a <= 3; a++, b-- ))
do
echo "$a - $b"
done
$ ./test9
1 - 10
2 - 9
3 - 8
3.3 while命令

3.3.1 while命令

while命令某种意义上是if-then语句和for循环的混合体。while命令定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码0。while会在每次迭代的一开始测试test命令,在test命令返回非零退出码时,while命令会停止执行那组命令。

#while的基本格式
while test command
do
	other commands
done

while命令中定义的testcommandif-then语句中的格式一模一样。可以使用任何普通的的bash shell命令,或者用test命令进行条件测试。while命令的关键在于所指定的test command的退出状态码必须随着循环中运行的命令而改变。如果退出状态码不发生变化,while循环就将一直不停地进行下去。最常见的test command用法是用方括号来检查循环命令中用到的shell变量的值。

$ cat test10
#!/bin/bash
# while command test

var1=10
while [ $var1 -gt 7 ]
do
echo $var1
var1=$[ $var1 - 1 ]
done
$ ./test10
10
9
8

3.3.2 使用多个测试命令

while命令允许你在while语句行定义多个测试命令。只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环。

$ cat test11
#!/bin/bash
# testing a multicommand while loop
var1=10
while echo $var1
[ $var1 -ge 0 ]
do
echo "This is inside the loop"
var1=$[ $var1 - 1 ]
done
$ ./test11
10
This is inside the loop
9
This is inside the loop
...

This is inside the loop
-1
$

最后var1值为0时,继续执行循环,循环体使var1值继续减一,进入判断打印出var1的值为-1,终止循环。

3.4 until命令

until命令和while命令工作的方式完全相反。until命令要求你指定一个通常返回非零退出状态码的测试命令。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。一旦测试命令返回了退出状态码0,循环就结束了。

until test commands
do
	other commands
done

while命令类似,可以在until命令语句中放入多个测试命令。只有最后一个命令的退出状态码决定了bash shell是否执行已定义的other commands

$ cat test12
#!/bin/bash
# using the until command
var1=100
until [ $var1 -eq 0 ]
do
echo $var1
var1=$[ $var1 - 25 ]
done
$ ./test12
100
75
50
25
$

shell会执行指定的多个测试命令,只有在最后一个命令成立时停止。

$ cat test13
#!/bin/bash
# using the until command
var1=100
until echo $var1
[ $var1 -eq 0 ]
do
echo Inside the loop: $var1
var1=$[ $var1 - 25 ]
done
$ ./test13
100
Inside the loop: 100
75
Inside the loop: 75
50
Inside the loop: 50
25
Inside the loop: 25
0
$
3.5 嵌套循环

循环语句中可以使用任意类型的命令,包括其他循环,这种循环叫做嵌套循环(nested loop)。在使用嵌套循环时,是使用迭代中使用迭代,与命令运行的次数是乘积关系。

$ cat test14
#!/bin/bash
# nesting for loops
for (( a = 1; a <= 3; a++ ))
do
	echo "Starting loop $a:"
	for (( b = 1; b <= 3; b++ ))
	do
	echo " Inside loop: $b"
	done
done
$ ./test14
Starting loop 1:
	Inside loop: 1
	Inside loop: 2
	Inside loop: 3
Starting loop 2:
	Inside loop: 1
	Inside loop: 2
	Inside loop: 3
Starting loop 3:
	Inside loop: 1
	Inside loop: 2
	Inside loop: 3
$

内部循环会在外部循环的每次迭代中遍历一次它所有的值。

还可以混用其他循环,如whilefor循环,untilwhile循环。

3.6 循环处理文件数据

遍历存储文件中的数据需要使用两种技术:

  • 使用嵌套循环
  • 修改IFS环境变量

通过修改IFS环境变量,就能强制for命令将文件中的每行都当成单独的一个条目来处理,即使数据中有空格也是如此。

#!/bin/bash
# changing the IFS value
IFS.OLD=$IFS
IFS=$'\n'
for entry in $(cat /etc/passwd)
do
echo "Values in $entry –"
IFS=:
for value in $entry
	do
	echo " $value"
	done
done
$

这个脚本使用了两个不同的IFS值来解析数据,第一个IFS值解析出/etc/passwd文件中的单独行。内部for循环接着将IFS的值修改为冒号,允许你从/etc/passwd的行中解析出单独的值。上面示例脚本输出为:

Values in rich:x:501:501:Rich Blum:/home/rich:/bin/bash -
rich
x
501
501
Rich Blum
/home/rich
/bin/bash
Values in katie:x:502:502:Katie Blum:/home/katie:/bin/bash -
katie
x
506
509
Katie Blum
/home/katie
/bin/bash
3.7 控制循环

控制循环有两个命令:

  • break命令
  • continue命令

3.7.1 break命令

break命令是退出循环的一个简单方法, 可以用break命令来退出任意类型的循环,包括whileuntil循环。

跳出单个循环

$ cat test17
#!/bin/bash
# breaking out of a for loop
for var1 in 1 2 3 4 5 6 7 8 9 10
do
	if [ $var1 -eq 5 ]
	then
		break
	fi
	echo "Iteration number: $var1"
done
echo "The for loop is completed"

$ ./test17
Iteration number: 1
Iteration number: 2
Iteration number: 3
Iteration number: 4
The for loop is completed
$

跳出内部循环

在处理多个循环时,break命令会自动终止你所在的最内层的循环。

$ cat test19
#!/bin/bash
# breaking out of an inner loop
for (( a = 1; a < 4; a++ ))
do
	echo "Outer loop: $a"
	for (( b = 1; b < 100; b++ ))
	do
		if [ $b -eq 5 ]
		then
			break
		fi
		echo " Inner loop: $b"
	done
done
	
Outer loop: 1
	Inner loop: 1
	Inner loop: 2
	Inner loop: 3
	Inner loop: 4
Outer loop: 2
	Inner loop: 1
	Inner loop: 2
	Inner loop: 3
	Inner loop: 4
Outer loop: 3
	Inner loop: 1
	Inner loop: 2
	Inner loop: 3
	Inner loop: 4

跳出外部循环

break命令接受单个命令行参数值

break n

其中n指定了要跳出的循环层级。默认情况下n为1,表明跳出的是当前的循环。如果将n设为2,break命令就会停止下一级的外部循环。

$ cat test20
#!/bin/bash
# breaking out of an outer loop
for (( a = 1; a < 4; a++ ))
do
	echo "Outer loop: $a"
	for (( b = 1; b < 100; b++ ))
	do
		if [ $b -gt 4 ]
		then
			break 2
		fi
		echo " Inner loop: $b"
	done
done

$ ./test20
Outer loop: 1
Inner loop: 1
Inner loop: 2
Inner loop: 3
Inner loop: 4

3.7.2 continue命令

continue命令可以提前中止某次循环中的命令,但并不会完全终止整个循环。

$ cat test21
#!/bin/bash
# using the continue command
for (( var1 = 1; var1 < 15; var1++ ))
do
	if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
	then
		continue
	fi
	echo "Iteration number: $var1"
	done
	
$ ./test21
Iteration number: 1
Iteration number: 2
Iteration number: 3
Iteration number: 4
Iteration number: 5
Iteration number: 10
Iteration number: 11
Iteration number: 12
Iteration number: 13
Iteration number: 14

如果var1大于并且小于10则调过后面打印命令开始递增进行下次循环判断。

也可以whileuntil循环中使用continue命令。

break命令一样,continue命令也允许向通过命令行参数指定要继续执行哪一级循环:

continue n

其中n定义了要继续的循环层级。

$ cat test22
#!/bin/bash
# continuing an outer loop
for (( a = 1; a <= 5; a++ ))
do
	echo "Iteration $a:"
	for (( b = 1; b < 3; b++ ))
	do
		if [ $a -gt 2 ] && [ $a -lt 4 ]
		then
			continue 2
		fi
		var3=$[ $a * $b ]
		echo " The result of $a * $b is $var3"
	done
done

$ ./test22
Iteration 1:
	The result of 1 * 1 is 1
	The result of 1 * 2 is 2
Iteration 2:
	The result of 2 * 1 is 2
	The result of 2 * 2 is 4
Iteration 3:
Iteration 4:
	The result of 4 * 1 is 4
	The result of 4 * 2 is 8
Iteration 5:
	The result of 5 * 1 is 5
	The result of 5 * 2 is 10
$

a值为3时,跳至外部循环,a值自增1,进行下次循环条件判断。

3.8 处理循环的输出

在shell脚本中,可以对循环的输出使用管道或进行重定向。可以通过在done命令之后添加一个处理命令来实现。

for file in /home/rich/*
do
	if [ -d "$file" ]
	then
		echo "$file is a directory"
	elif
		echo "$file is a file"
	fi
done > output.txt

shell会将for命令的结果重定向到文件output.txt中,而不是显示在屏幕上。

$ cat test23
#!/bin/bash
# redirecting the for output to a file
for (( a = 1; a < 10; a++ ))
do
	echo "The number is $a"
#shell创建了文件test123.txt并将for命令的输出重定向到这个文件
done > test23.txt
#shell在for命令之后正常显示了echo语句
echo "The command is finished."

$ ./test23
The command is finished.

$ cat test23.txt
The number is 1
The number is 2
The number is 3
The number is 4
The number is 5
The number is 6
The number is 7
The number is 8
The number is 9

还可以将循环的结果使用管道命令给另一个命令。

$ cat test24
#!/bin/bash
# piping a loop to another command
for state in "North Dakota" Connecticut Illinois Alabama Tennessee
do
	echo "$state is the next place to go"
#sort命令会为for循环的输出结果排序
done | sort
echo "This completes our travels"

$ ./test24
Alabama is the next place to go
Connecticut is the next place to go
Illinois is the next place to go
North Dakota is the next place to go
Tennessee is the next place
3.9 实例

循环是对系统数据进行迭代的常用方法,无论是目录中的文件还是文件中的数据。

3.9.1 查找可执行文件

当从命令行中运行一个程序时,Linux系统会搜索一些目录来查找对应的文件。这些目录被定义在环境变量PATH中。如果你想找出系统中有哪些可执行文件,只需要扫描PATH环境变量中的所有目录就可以了。

$ cat test25
#!/bin/bash
# finding files in the PATH

#定义IFS分隔符
IFS=:
#获取所有PATH中的目录到folder中
for folder in $PATH
do
	echo "$folder:"
	#对每个目录中的文件进行遍历
	for file in $folder/*
	do
		#如果具有可执行权限,打印
		if [ -x $file ]
		then
			echo " $file"
		fi
	done
done

$ ./test25 | more
/usr/local/bin:
/usr/bin:
/usr/bin/Mail
/usr/bin/Thunar
/usr/bin/X
...

3.9.2 创建多个用户账户

创建多个用户时,手动使用useradd命令效率太低,可以将需要添加的新用户账户放在一个文本文件中,然后创建一个简单的脚本进行处理。文本的格式为:

userid, username

第一个为创建新用户的ID,第二个是用户的名称,两个值之间使用逗号分隔,这样就形成了一种名为逗号分隔值的文件格式(或者是.csv)。

$ cat test26
#!/bin/bash
# process new user accounts

#$input变量指向数据文件
input="users.csv"

#read命令会自动读取.csv文本文件的下一行内容,所以不需要专门写一个循环来处理,当read命令返回FALSE时(也就是读取整个文件时),while命令就会自动退出
while IFS=',' read -r userid name
do
	echo "adding $userid"
	useradd -c "$name" -m $userid
#$input作为while命令的重定向数据
done < "$input"

$ cat users.csv
rich,Richard Blum
christine,Christine Bresnahan
barbara,Barbara Blum
tim,Timothy Bresnahan

#必须作为root用户才能运行这个脚本,因为useradd命令需要root权限
# ./test26
adding rich
adding christine
adding barbara
adding tim

本章小结

介绍了shell中循环相关的命令

1.for命令

2.while命令

3.until命令

4.循环控制命令break,continue

5.循环输入输出重定向

6.循环相关的两个实例

参考文献:Linux命令行与shell脚本编程大全(第三版)Richard Blum Christine Bresnahan著 门佳 武海峰译

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖小张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值