loops1.html

来自「BASH Shell 编程 经典教程 《高级SHELL脚本编程》中文版」· HTML 代码 · 共 1,622 行 · 第 1/2 页

HTML
1,622
字号
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><HTML><HEAD><TITLE>循环</TITLE><METANAME="GENERATOR"CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINKREL="HOME"TITLE="高级Bash脚本编程指南"HREF="index.html"><LINKREL="UP"TITLE="循环与分支"HREF="loops.html"><LINKREL="PREVIOUS"TITLE="循环与分支"HREF="loops.html"><LINKREL="NEXT"TITLE="嵌套循环"HREF="nestedloops.html"></HEAD><BODYCLASS="SECT1"BGCOLOR="#FFFFFF"TEXT="#000000"LINK="#0000FF"VLINK="#840084"ALINK="#0000FF"><DIVCLASS="NAVHEADER"><TABLESUMMARY="Header navigation table"WIDTH="100%"BORDER="0"CELLPADDING="0"CELLSPACING="0"><TR><THCOLSPAN="3"ALIGN="center">高级Bash脚本编程指南: 一本深入学习shell脚本艺术的书籍</TH></TR><TR><TDWIDTH="10%"ALIGN="left"VALIGN="bottom"><AHREF="loops.html"ACCESSKEY="P">前一页</A></TD><TDWIDTH="80%"ALIGN="center"VALIGN="bottom">10. 循环与分支</TD><TDWIDTH="10%"ALIGN="right"VALIGN="bottom"><AHREF="nestedloops.html"ACCESSKEY="N">下一页</A></TD></TR></TABLE><HRALIGN="LEFT"WIDTH="100%"></DIV><DIVCLASS="SECT1"><H1CLASS="SECT1"><ANAME="LOOPS1">10.1. 循环</A></H1><P><ICLASS="FIRSTTERM">循环</I>就是<ICLASS="FIRSTTERM">迭代</I>(重复)一些命令的代码块, 如果<ICLASS="FIRSTTERM">循环控制条件</I>不满足的话, 就结束循环.		</P><P></P><DIVCLASS="VARIABLELIST"><P><B><ANAME="FORLOOPREF1"></A>for循环</B></P><DL><DT><BCLASS="COMMAND">for <CODECLASS="VARNAME">arg</CODE> in <TTCLASS="REPLACEABLE"><I>[list]</I></TT></B></DT><DD><P>这是一个基本的循环结构. 它与C语言中的for循环结构有很大的不同.	      </P><P><P><BCLASS="COMMAND">for</B>   <TTCLASS="REPLACEABLE"><I>arg</I></TT>   in  [<TTCLASS="REPLACEABLE"><I>list</I></TT>]<BR>  do <BR>  <TTCLASS="REPLACEABLE"><I>燾ommand(s)</I></TT>... <BR>  done </P></P><DIVCLASS="NOTE"><P></P><TABLECLASS="NOTE"WIDTH="90%"BORDER="0"><TR><TDWIDTH="25"ALIGN="CENTER"VALIGN="TOP"><IMGSRC="./images/note.gif"HSPACE="5"ALT="Note"></TD><TDALIGN="LEFT"VALIGN="TOP"><P>在循环的每次执行中, 				  <TTCLASS="REPLACEABLE"><I>arg</I></TT>将顺序的访问<TTCLASS="REPLACEABLE"><I>list</I></TT>中列出的变量.		  </P></TD></TR></TABLE></DIV><P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;for arg in "$var1" "$var2" "$var3" ... "$varN"    2&nbsp;# 在第1次循环中, arg = $var1	      3&nbsp;# 在第2次循环中, arg = $var2	      4&nbsp;# 在第3次循环中, arg = $var3	      5&nbsp;# ...  6&nbsp;# 在第N此循环中, arg = $varN  7&nbsp;  8&nbsp;# 在[list]中的参数加上双引号是为了阻止单词分割.</PRE></FONT></TD></TR></TABLE></P><P><TTCLASS="REPLACEABLE"><I>list</I></TT>中的参数允许包含通配符. </P><P><ANAME="NEEDSEMICOLON"></A></P><P>如果<ICLASS="FIRSTTERM">do</I>和<ICLASS="FIRSTTERM">for</I>想在同一行中出现,				那么在它们之间需要添加一个分号. </P><P><P><BCLASS="COMMAND">for</B>   <TTCLASS="REPLACEABLE"><I>arg</I></TT>   in  [<TTCLASS="REPLACEABLE"><I>list</I></TT>]  ;   do <BR></P></P><DIVCLASS="EXAMPLE"><HR><ANAME="EX22"></A><P><B>例子 10-1. 一个简单的<BCLASS="COMMAND">for</B>循环</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# 列出所有的行星名称. (译者注: 现在的太阳系行星已经有了变化^_^)  3&nbsp;  4&nbsp;for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto  5&nbsp;do  6&nbsp;  echo $planet  # 每个行星都被单独打印在一行上.  7&nbsp;done  8&nbsp;  9&nbsp;echo 10&nbsp; 11&nbsp;for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto" 12&nbsp;# 所有的行星名称都打印在同一行上. 13&nbsp;# 整个'list'都被双引号封成了一个变量.  14&nbsp;do 15&nbsp;  echo $planet 16&nbsp;done 17&nbsp; 18&nbsp;exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV><DIVCLASS="NOTE"><P></P><TABLECLASS="NOTE"WIDTH="90%"BORDER="0"><TR><TDWIDTH="25"ALIGN="CENTER"VALIGN="TOP"><IMGSRC="./images/note.gif"HSPACE="5"ALT="Note"></TD><TDALIGN="LEFT"VALIGN="TOP"><P>每个<KBDCLASS="USERINPUT">[list]</KBD>中的元素都可能包含多个参数. 	      在处理参数组时, 这是非常有用的.	      在这种情况下, 使用<AHREF="internal.html#SETREF">set</A>命令(参见 <AHREF="internal.html#EX34">例子 11-15</A>)来强制解析每个<KBDCLASS="USERINPUT">[list]</KBD>中的元素, 并且将每个解析出来的部分都分配到一个位置参数中. </P></TD></TR></TABLE></DIV><DIVCLASS="EXAMPLE"><HR><ANAME="EX22A"></A><P><B>例子 10-2. 每个[list]元素中都带有两个参数的<BCLASS="COMMAND">for</B>循环</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# 还是行星.  3&nbsp;  4&nbsp;# 用行星距太阳的距离来分配行星的名字.  5&nbsp;  6&nbsp;for planet in "Mercury 36" "Venus 67" "Earth 93"  "Mars 142" "Jupiter 483"  7&nbsp;do  8&nbsp;  set -- $planet  # 解析变量"planet"并且设置位置参数.   9&nbsp;  # "--" 将防止$planet为空, 或者是以一个破折号开头.  10&nbsp; 11&nbsp;  # 可能需要保存原始的位置参数, 因为它们被覆盖了. 12&nbsp;  # 一种方法就是使用数组. 13&nbsp;  #        original_params=("$@") 14&nbsp; 15&nbsp;  echo "$1		$2,000,000 miles from the sun" 16&nbsp;  #-------two  tabs---把后边的0和2连接起来 17&nbsp;done 18&nbsp; 19&nbsp;# (感谢, S.C., 对此问题进行的澄清.) 20&nbsp; 21&nbsp;exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV><P>可以将一个变量放在<ICLASS="FIRSTTERM">for循环</I>的<KBDCLASS="USERINPUT">[list]</KBD>位置上. </P><DIVCLASS="EXAMPLE"><HR><ANAME="FILEINFO"></A><P><B>例子 10-3. <EM>文件信息:</EM> 对包含在变量中的文件列表进行操作</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# fileinfo.sh  3&nbsp;  4&nbsp;FILES="/usr/sbin/accept  5&nbsp;/usr/sbin/pwck  6&nbsp;/usr/sbin/chroot  7&nbsp;/usr/bin/fakefile  8&nbsp;/sbin/badblocks  9&nbsp;/sbin/ypbind"     # 这是你所关心的文件列表. 10&nbsp;                  # 扔进去一个假文件, /usr/bin/fakefile. 11&nbsp; 12&nbsp;echo 13&nbsp; 14&nbsp;for file in $FILES 15&nbsp;do 16&nbsp; 17&nbsp;  if [ ! -e "$file" ]       # 检查文件是否存在. 18&nbsp;  then 19&nbsp;    echo "$file does not exist."; echo 20&nbsp;    continue                # 继续下一个. 21&nbsp;   fi 22&nbsp; 23&nbsp;  ls -l $file | awk '{ print $9 "         file size: " $5 }'  # 打印两个域. 24&nbsp;  whatis `basename $file`   # 文件信息. 25&nbsp;  # 注意whatis数据库需要提前建立好. 26&nbsp;  # 要想达到这个目的, 以root身份运行/usr/bin/makewhatis. 27&nbsp;  echo 28&nbsp;done   29&nbsp; 30&nbsp;exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV><P>如果在<ICLASS="FIRSTTERM">for循环</I>的<KBDCLASS="USERINPUT">[list]</KBD>中有通配符			(<SPANCLASS="TOKEN">*</SPAN>和<SPANCLASS="TOKEN">?</SPAN>), 那么将会发生<AHREF="globbingref.html">通配(globbing)</A>, 		   也就是文件名扩展. </P><DIVCLASS="EXAMPLE"><HR><ANAME="LISTGLOB"></A><P><B>例子 10-4. <BCLASS="COMMAND">在for循环中操作文件</B></B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# list-glob.sh: 使用"globbing", 在for循环中产生[list]  3&nbsp;  4&nbsp;echo  5&nbsp;  6&nbsp;for file in *  7&nbsp;#           ^  在表达式中识别文件名匹配时,  8&nbsp;#+             Bash将执行文件名扩展.  9&nbsp;do 10&nbsp;  ls -l "$file"  # 列出在$PWD(当前目录)中的所有文件. 11&nbsp;  #  回想一下,通配符"*"能够匹配所有文件, 12&nbsp;  #+ 然而,在"globbing"中,是不能比配"."文件的. 13&nbsp; 14&nbsp;  #  如果没匹配到任何文件,那它将扩展成自己. 15&nbsp;  #  为了不让这种情况发生,那就设置nullglob选项 16&nbsp;  #+   (shopt -s nullglob). 17&nbsp;  #  感谢, S.C. 18&nbsp;done 19&nbsp; 20&nbsp;echo; echo 21&nbsp; 22&nbsp;for file in [jx]* 23&nbsp;do 24&nbsp;  rm -f $file    # 只删除当前目录下以"j"或"x"开头的文件. 25&nbsp;  echo "Removed file \"$file\"". 26&nbsp;done 27&nbsp; 28&nbsp;echo 29&nbsp; 30&nbsp;exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV><P>在一个<ICLASS="FIRSTTERM">for循环</I>中忽略<KBDCLASS="USERINPUT">in [list]</KBD>部分的话, 			将会使循环操作<SPANCLASS="TOKEN">$@</SPAN> -- 从命令行传递给脚本的<AHREF="internalvariables.html#POSPARAMREF">位置参数</A>. 			一个非常好的例子, 参见<AHREF="contributed-scripts.html#PRIMES">例子 A-16</A>. 参见<AHREF="internal.html#REVPOSPARAMS">例子 11-16</A>.</P><DIVCLASS="EXAMPLE"><HR><ANAME="EX23"></A><P><B>例子 10-5. 在<BCLASS="COMMAND">for</B>循环中省略<KBDCLASS="USERINPUT">in [list]</KBD>部分</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;  3&nbsp;#  使用两种方式来调用这个脚本, 一种带参数, 另一种不带参数,  4&nbsp;#+ 并观察在这两种情况下, 此脚本的行为.  5&nbsp;  6&nbsp;for a  7&nbsp;do  8&nbsp; echo -n "$a "  9&nbsp;done 10&nbsp; 11&nbsp;#  省略'in list'部分, 因此循环将会操作'$@' 12&nbsp;#+ (包括空白的命令行参数列表). 13&nbsp; 14&nbsp;echo 15&nbsp; 16&nbsp;exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV><P>也可以使用<AHREF="commandsub.html#COMMANDSUBREF">命令替换</A>	      来产生<ICLASS="FIRSTTERM">for循环</I>的<KBDCLASS="USERINPUT">[list]</KBD>.	      参见<AHREF="extmisc.html#EX53">例子 12-49</A>,	      <AHREF="loops1.html#SYMLINKS">例子 10-10</A>和<AHREF="mathc.html#BASE">例子 12-43</A>.</P><DIVCLASS="EXAMPLE"><HR><ANAME="FORLOOPCMD"></A><P><B>例子 10-6. 使用命令替换来产生<BCLASS="COMMAND">for</B>循环的[list]</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;#  for-loopcmd.sh: 带[list]的for循环,   3&nbsp;#+ [list]是由命令替换所产生的.  4&nbsp;  5&nbsp;NUMBERS="9 7 3 8 37.53"  6&nbsp;  7&nbsp;for number in `echo $NUMBERS`  # for number in 9 7 3 8 37.53  8&nbsp;do  9&nbsp;  echo -n "$number " 10&nbsp;done 11&nbsp; 12&nbsp;echo  13&nbsp;exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV><P>下边是一个用命令替换来产生[list]的更复杂的例子. </P><DIVCLASS="EXAMPLE"><HR><ANAME="BINGREP"></A><P><B>例子 10-7. 对于二进制文件的<AHREF="textproc.html#GREPREF">grep</A>替换</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# bin-grep.sh: 在一个二进制文件中定位匹配字串.  3&nbsp;  4&nbsp;# 对于二进制文件的"grep"替换.   5&nbsp;# 与"grep -a"的效果相似  6&nbsp;  7&nbsp;E_BADARGS=65  8&nbsp;E_NOFILE=66  9&nbsp; 10&nbsp;if [ $# -ne 2 ] 11&nbsp;then 12&nbsp;  echo "Usage: `basename $0` search_string filename" 13&nbsp;  exit $E_BADARGS 14&nbsp;fi 15&nbsp; 16&nbsp;if [ ! -f "$2" ] 17&nbsp;then 18&nbsp;  echo "File \"$2\" does not exist." 19&nbsp;  exit $E_NOFILE 20&nbsp;fi   21&nbsp; 22&nbsp; 23&nbsp;IFS=$'\012'       # 由Paulo Marcel Coelho Aragao提出的建议. 24&nbsp;                  # 也就是:  IFS="\n" 25&nbsp;for word in $( strings "$2" | grep "$1" ) 26&nbsp;# "strings" 命令列出二进制文件中的所有字符串. 27&nbsp;# 输出到管道交给"grep",然后由grep命令来过滤字符串. 28&nbsp;do 29&nbsp;  echo $word 30&nbsp;done 31&nbsp; 32&nbsp;# S.C. 指出, 行23 - 29 可以被下边的这行来代替, 33&nbsp;#    strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]' 34&nbsp; 35&nbsp; 36&nbsp;# 试试用"./bin-grep.sh mem /bin/ls"来运行这个脚本. 37&nbsp; 38&nbsp;exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV><P>大部分相同. </P><DIVCLASS="EXAMPLE"><HR><ANAME="USERLIST"></A><P><B>例子 10-8. 列出系统上的所有用户</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# userlist.sh  3&nbsp;  4&nbsp;PASSWORD_FILE=/etc/passwd  5&nbsp;n=1           # User number  6&nbsp;  7&nbsp;for name in $(awk 'BEGIN{FS=":"}{print $1}' &#60; "$PASSWORD_FILE" )  8&nbsp;# 域分隔 = :             ^^^^^^  9&nbsp;# 打印出第一个域                 ^^^^^^^^ 10&nbsp;# 从password文件中取得输入                     ^^^^^^^^^^^^^^^^^ 11&nbsp;do 12&nbsp;  echo "USER #$n = $name" 13&nbsp;  let "n += 1" 14&nbsp;done   15&nbsp; 16&nbsp; 17&nbsp;# USER #1 = root 18&nbsp;# USER #2 = bin 19&nbsp;# USER #3 = daemon 20&nbsp;# ... 21&nbsp;# USER #30 = bozo 22&nbsp; 23&nbsp;exit 0 24&nbsp; 25&nbsp;#  练习: 26&nbsp;#  ----- 27&nbsp;#  一个普通用户(或者是一个普通用户运行的脚本) 28&nbsp;#+ 怎么才能够读取/etc/passwd呢? 29&nbsp;#  这是否是一个安全漏洞? 为什么是?为什么不是?</PRE></FONT></TD></TR></TABLE><HR></DIV><P>关于用命令替换来产生[list]的最后一个例子. </P><DIVCLASS="EXAMPLE"><HR><ANAME="FINDSTRING"></A><P><B>例子 10-9. 在目录的所有文件中查找源字串</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="90%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING">  1&nbsp;#!/bin/bash  2&nbsp;# findstring.sh:  3&nbsp;# 在一个指定目录的所有文件中查找一个特定的字符串.  4&nbsp;  5&nbsp;directory=/usr/bin/  6&nbsp;fstring="Free Software Foundation"  # 查看哪个文件中包含FSF.  7&nbsp;  8&nbsp;for file in $( find $directory -type f -name '*' | sort )  9&nbsp;do 10&nbsp;  strings -f $file | grep "$fstring" | sed -e "s%$directory%%" 11&nbsp;  #  在"sed"表达式中, 12&nbsp;  #+ 我们必须替换掉正常的替换分隔符"/", 13&nbsp;  #+ 因为"/"碰巧是我们需要过滤的字符串之一. 14&nbsp;  #  如果不用"%"代替"/"作为分隔符,那么这个操作将失败,并给出一个错误消息.(试一试). 15&nbsp;done   16&nbsp; 17&nbsp;exit 0 18&nbsp; 19&nbsp;#  练习 (很简单): 20&nbsp;#  --------------- 21&nbsp;#  转换这个脚本, 用命令行参数 22&nbsp;#+ 代替内部用的$directory和$fstring.</PRE></FONT

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?