
在日常工作中,学习使用Shell编程可以在很大程度上代替手动重复性工作并提高工作效率. 从这一点开始,了解如何在Shell中编写循环非常重要. 下面介绍了shell中的while循环和for循环.
常见的while和for循环通过以下方式编写:
(1)通过输入重定向到while循环
while read line
do
echo $line
done < file(待读取的文件)
(2)通过cat命令输出重定向到while循环
cat file(待读取的文件) | while read line
do
echo $line
done
(3)用于循环读取命令输出
for line in `cat file(待读取的文件)`
do
echo $line
done

根据我的理解,上例中的while和for循环之间的区别在于while循环会将每一行的内容读入line变量;在for循环中,读入的内容将为环境变量(内部字段分隔符,字段分隔符)之间的边界,然后将分隔的内容一一读入变量行. 本质上,for循环读取字段shell脚本执行for循环,但是IFS可以设置为\ n以便可以逐行读取.
为了便于测试,我们使用echo命令输出多行文本. 其中,echo命令的-e选项意味着可以识别转义字符并可以输出行分隔符. 以下示例:
$ echo -e "a 12\nb 10"
a 12
b 10
$
(1)逐行读取文件时
$ echo -e "a 12\nb 10" | while read line
> do
> echo $line
> done
a 12
b 10
$
(2)for循环的默认行为
$ for line in `echo -e "a 12\nb 10"`
> do
> echo $line
> done
a
12
b
10
$
(3)通过更改IFS实现按行读取循环

$ IFS=$'\n'
$ for line in `echo -e "a 12\nb 10"`
> do
> echo $line
> done
a 12
b 10
$
除了编写上述公共循环外,循环逐行读取时,还可以根据IFS将整行划分为多个字段,并为变量名分配值,然后依次读取. 如果变量数大于实际字段数,则变量数少的字段为空;否则,变量为空. 如果变量数小于实际字段数,则最后一个变量将接收所有后续字段的所有订单. 这是一个示例:
$ echo -e "Tom 13\nLily 10 120cm\nJohn" | while read name age
> do
> echo "${name}: ${age}"
> done
Tom: 13
Lily: 10 120cm
John:
$
假设有这样一个场景,您需要在目录中找到许多关键字. 如果使用外壳程序完成,则需要先将要搜索的关键字写到文件中,例如keyword.txt,每行一个关键字. 然后,编写脚本以读取文件,删除每个关键字,然后使用grep命令找到它. 以下是参考脚本的示例:
keyword_file='keyword.txt'
search_dir='/xx/path/'
result_file=result.txt
echo "Results:" | tee $result_file
cat $keyword_file | while read keyword
do
echo "${keyword}:" | tee -a $result_file
#word match, recursively search in directory and sub directory. only .java file will be searched. case insensitive. -l means only list file name
grep -irw --include="*.java" "$keyword" "$search_dir" -l | tee -a $result_file
echo "" | tee -a $result_file
done
结果如下:
$ cat keyword.txt
Polymerize
SortMeta
DataTube
$ sh search.sh
Results:
Polymerize:
/xx/path/src/com/poly/merge/test/TestMergeSortDesc.java
/xx/path/src/com/poly/merge/test/TestMergeSortDescMultiSort.java
/xx/path/src/com/poly/merge/basic/Polymerize.java
SortMeta:
/xx/path/src/com/poly/merge/test/TestMergeSort.java
/xx/path/src/com/poly/merge/test/TestMergeSort16.java
DataTube:
/xx/path/UnitTest/com/poly/merge/basic/PolymerizeTest.java
/xx/path/src/com/poly/merge/test/TestMergeSort.java
/xx/path/src/com/poly/merge/test/DataTubeImp16.java
/xx/path/src/com/poly/merge/basic/Polymerize.java
/xx/path/src/com/poly/merge/basic/DataTube.java
$
注意: 此处的关键字文件涉及按行读取文件,因此在此处应注意,行分隔符必须为Unix / OS X样式LF,即\ n. 如果文件的行分隔符是Windows样式的CRLF,即\ r \ n,则不会找到任何内容. (当我在Windows计算机上将关键字文件分隔符设置为CRLF时,我使用git bash运行搜索脚本,然后一无所获,最后发现了行分隔符的问题. 将行分隔符更改为LF之后,搜索会没事的. )

使用Shell编写逐行读取文件的脚本时,经常会遇到行分隔符的坑,这种现象看起来很奇怪. 在这里解释.
说明:
在vim中,可以使用以下命令: set ff = xxx更改文本文件的行分隔符(ff是fileformat的缩写). 其中xxx可以是dos(代表Windows风格的行分隔符,即CRLF,\ r \ n),unix(代表Unix和OS X风格的行分隔符,即LF,\ n)或mac(代表Mac风格)行分隔符,CR,\ r).
假设存在一个包含以下内容的文件,则行分隔符采用Windows格式\ r \ n:
$ cat text.txt
in the house, there is a little horse.
finally, it won over a long race near the small inn.
all above is just a makeup story.
vaginally
编写以下shell脚本test.sh,逐行读取文件,然后输出:
cat text.txt | while read line
do
echo AAAA${line}BB;
done
输出如下:

$ sh test.sh
BBAAin the house, there is a little horse.
BBAAfinally, it won over a long race near the small inn.
BBAAall above is just a makeup story.
BBAAvaginally
此输出非常奇怪. AAAA和BB不会按预期显示在每行内容的两侧. BB改写了AAAA中的前两个AA. 原因是在此处按行读取时,分隔符为\ n,但是文本文件的行分隔符为\ r \ n. 这样,在每一行读出的内容的末尾都有一个\ r. 这个字符意味着输入,这意味着将光标移动到行首. 这样,以第一行为例,在房子里输出BBBB,有一匹小马. 之后shell脚本执行for循环,输出\ r将光标移动到行的开头,然后输出AA覆盖最前面的BB,然后出现先前的效果.
The
\ r字符并非始终都是坑. 例如,如果要在命令行上显示跳动时间,则需要使用此字符使后面的输出位于前面. 这是time.sh的示例:
i=0
while [ $i -lt 30 ];
do echo -ne "\r"`date` #you should remove new line too;
sleep 1;
i=$(($i + 1));
done
我在MacOS随附的命令行上执行上述脚本. 如果我直接将脚本内容复制到命令行以执行,那么此时,我将获得殴打时间,并且不会输出多行.
如果由命令sh time.sh执行,则输出效果是逐行输出. 怀疑sh指定的shell不是Bash,并且echo命令在选项ne的支持下可能有问题(可以看出echo不是非常可移植的,请在编写脚本时尝试使用printf). ,看完后发现是:
$ which sh
/bin/sh
通过添加执行权限并手动指定Bash执行,可以达到跳动的效果,如下所示:
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-233923-1.html
3333333333333333