Linux-Shell循环

背景

之前在《Linux-shell与环境变量》中,讲述了条件判断语句,那么还缺少循环结构,本文就对shell中的循环结构做简要描述。笔记源自于《Linux命令行与shell编程基础》

内容

for循环

条件判断固然解决了多情况下难以统一解决方案的问题,但是对于重复劳作的问题却还没有解决,所以shell中还有循环结构的命令:for、while、until。针对for命令的结构如下:

1
2
3
4
for var in list
do
commands
done

for循环中的list说是一个列表,既可以是列表变量,也可以是直接的列表内容,但本质上就是一个字符串,比如:

1
2
3
4
5
# 直接将列表的值写在了in后面
for i in alabama alaska linux shell wahaha
do
echo "the var is $i"
done

同样可以借助于变量

1
2
3
4
5
6
7
8
# 请注意,for默认使用空格、换行符、制表符作为列表元素的间隔符
var="alabama alaska linux shell wahaha"
# 直接在变量后追加元素
var=$var" niubi"
for i in $var
do
echo "the var is $i"
done

but,上面的方式在遇到'、"、[、空格等特殊字符的时候就会存在错误,这个时候就需要进行转义,可以使用"进行包裹,或者使用\进行转义:

1
2
3
4
5
# 注意:用\进行转义,或者用"包裹内容
for i in I\'m the "pretty boy"
do
echo "the var is $i"
done

同样,还可以通过命令替换实现对文件内容的遍历。

1
2
3
4
5
file="run.sh"
for i in $(cat $file)
do
echo i
done

不过,由于for循环中,默认使用空格、制表符、换行符作为列表元素的分隔符,所以输出的内容不尽如人意。因此需要在shell命令中根据需要自己进行修改内容的默认分隔符:

1
2
3
4
5
6
# 为了方便后期恢复,所以需要保留一下默认值,然后再修改$IFS的值
# 同时为了避免此处修改的内容影响后面shell,所以一旦使用完毕后就要将$IFS改回去
IFS_OLD=$IFS
IFS=$'\n'
shell命令
IFS=$IFS_OLD

当然,如果需要同时指定多个字符作为分隔符的标识,则直接在=后面拼接即可,需要注意的是:如果有与shell中冲突的符号,则需要利用\进行转义才行:

1
2
3
# 变量的起始必须为$
# 此处使用换行符、:、;、"作为分隔符的标识
IFS=$'\n':\;\"

并且,for循环还支持遍历本地的文件目录,不过需要使用通配符:

1
2
3
4
5
6
7
8
# 需要使用通配符,也就是文件的正则
# 同时可以使用多个路径
path=$(pwd)/*
path1=$(pwd)/../*
for i in $path $path1
do
echo "the file is $i"
done

(了解)然后就是for循环的另一种表达方式,并不常用

1
2
3
4
for (( variable asignment; condition; iterationprocess ))
do
statement
done

可以将其类比于C语言中的for循环理解,举个例子

1
2
3
4
5
# 你也发现了,里面可以同时定义多个变量
for (( a=1, b=10; a <= 10; a++, b-- ))
do
echo "$a - $b"
done

while、until循环

while循环与for循环不同,它是一种条件判断式的循环,是当型循环,格式如下:

1
2
3
4
while test command
do
statement
done

while循环中的test commandif -- then中的一样,只有当test command的返回状态码为0时,才会执行循环体,比如:

1
2
3
4
5
6
var=10
while [ $var -ne 0 ]
do
echo "the number is $var"
var=$[ $var - 1]
done

until循环也是一种条件判断式的循环,是直到型循环,格式如下:

1
2
3
4
until test command
do
statement
done

until循环中的test commandif -- then中的一样,只有当test command的返回状态码不为0时,才会执行循环体,比如:

1
2
3
4
until [ $var -eq 0 ]
do
statement
done

嵌套循环

shell中是支持循环嵌套的,这个和C一样,比如:

1
2
3
4
5
6
7
8
9
for (( i=1; i<=10; i++ ))
do
j=1
while [ $j -lt 5 ]
do
echo "$i - $j"
j=$[ $j + 1 ]
done
done

与其他语言一样,在shell中也有continue、break用于提前终止循环,并且它们的含义与在python是一样的,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (( i=1; i<=10; i++ ))
do
if [ $i -eq 5 ]
then
echo "$i is lower than 5, so continue"
continue # 跳过本次循环
elif [ $i -ge 8 ]
then
echo "$i is up than 8, so break"
break # 跳出循环
else
echo "$i is ok"
fi
done

不过这还不是其最神奇的地方,在其他高级语言中,单个的continue、break针对的都是其所在的循环体,而在shell中,continue、break可以通过添加数字的方式直接作用嵌套循环的外层,它的格式如下:

1
2
3
# 不写n的情况下,默认为1,表示当前的循环体,
continue n
break n

举个例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (( j=1; j<=10; j++ ))
do
for (( i=1; i<=10; i++ ))
do
if [ $i -eq 5 ]
then
echo "$i is lower than 5, so continue"
continue 2 # 跳过倒数第2层的当前循环
elif [ $i -ge 8 ]
then
echo "$i is up than 8, so break"
break 2 # 跳出倒数第2层的剩余循环
else
echo "$i is ok"
fi
done
done

循环的输出

对于循环,它和所有其他的shell命令一样,也支持重定向、管道的命令,只是它的重定向、管道是写在done 后面,并且它作用的内容是循环体命令的输出内容,结构如下:

1
2
3
4
5
# 以输出重定向为例,循环体中的命令输出会直接输入到1.txt文件中
for var in list
do
commands
done > 1.txt

举个例子:

1
2
3
4
5
6
7
#!/bin/bash
input="shuai.csv"
while IFS=',' read -r userid name
do
echo "adding $userid"
echo "add $name -m $userid"
done < "$input"