Linux/Python学习论坛-京峰教育

 找回密码
 立即注册

一键登录:

搜索
热搜: 活动 交友 discuz
查看: 2242|回复: 0

13) for what? while 与 until 差在哪?

[复制链接]

63

主题

161

帖子

2628

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
2628
发表于 2015-3-18 14:54:31 | 显示全部楼层 |阅读模式
for what? while 与 until 差在哪?
终于,来到 shell 十三问的最后一问了... 长长吐一口气~~~~
最后要介绍的是 shell script 设计中常见的"循环"(loop)。
所谓的 loop 就是 script 中的一段在一定条件下反复执行的代码。
bash shell 中常用的 loop 有如下三种:
* for
* while
* until
for loop 是从一个清单列表中读进变量值,并"依次"的循环执行 do 到 done 之间的命令行。
例:
代码:
for var in one two three four five
do
echo -----------
echo '$var is '$var
echo
done
上例的执行结果将会是:
1) for 会定义一个叫 var 的变量,其值依次是 one two three four five 。
2) 因为有 5 个变量值,因此 do 与 done 之间的命令行会被循环执行 5 次。
3) 每次循环均用 echo 产生三行句子。
而第二行中不在 hard quote 之内的 $var 会依次被替换为 one two three four
five 。
4) 当最后一个变量值处理完毕,循环结束。
我们不难看出,在 for loop 中,变量值的多寡,决定循环的次数。
然而,变量在循环中是否使用则不一定,得视设计需求而定。
倘若 for loop 没有使用 in 这个 keyword 来指定变量值清单的话,其值将从 $@ (或 $* )
中继承:
代码:
for var; do
....
done
(若你忘记了 positional parameter ,请温习第 9 章...)
for loop 用于处理"清单"(list)项目非常方便,
其清单除了可明确指定或从 positional parameter 取得之外,
也可从变量替换或命令替换取得... (再一次提醒:别忘了命令行的"重组"特性﹗)
然而,对于一些"累计变化"的项目(如整数加减),for 亦能处理:
代码:
for ((i=1;i<=10;i++))
do
echo "num is $i"
done
除了 for loop ,上面的例子我们也可改用 while loop 来做到:
代码:
num=1
while [ "$num" -le 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
while loop 的原理与 for loop 稍有不同:
它不是逐次处理清单中的变量值,而是取决于 while 后面的命令行之 return value :
* 若为 ture ,则执行 do 与 done 之间的命令,然后重新判断 while 后的 return value 。
* 若为 false ,则不再执行 do 与 done 之间的命令而结束循环。
分析上例:
1) 在 while 之前,定义变量 num=1 。
2) 然后测试(test) $num 是否小于或等于 10 。
3) 结果为 true ,于是执行 echo 并将 num 的值加一。
4) 再作第二轮测试,其时 num 的值为 1+1=2 ,依然小于或等于 10,因此为
true ,继续循环。
5) 直到 num 为 10+1=11 时,测试才会失败... 于是结束循环。
我们不难发现:
* 若 while 的测试结果永远为 true 的话,那循环将一直永久执行下去:
代码:
while :; do
echo looping...
done
上例的" : "是 bash 的 null command ,不做任何动作,除了送回 true 的 return value 。
因此这个循环不会结束,称作死循环。
死循环的产生有可能是故意设计的(如跑 daemon),也可能是设计错误。
若要结束死寻环,可透过 signal 来终止(如按下 ctrl-c )。
(关于 process 与 signal ,等日后有机会再补充,十三问暂时略过。)
一旦你能够理解 while loop 的话,那,就能理解 until loop :
* 与 while 相反,until 是在 return value 为 false 时进入循环,否则结束。
因此,前面的例子我们也可以轻松的用 until 来写:
代码:
num=1
until [ ! "$num" -le 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
或是:
代码:
num=1
until [ "$num" -gt 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
okay ,关于 bash 的三个常用的 loop 暂时介绍到这里。
在结束本章之前,再跟大家补充两个与 loop 有关的命令:
* break
* continue
这两个命令常用在复合式循环里,也就是在 do ... done 之间又有更进一层的 loop ,
当然,用在单一循环中也未尝不可啦... ^_^
break 是用来打断循环,也就是"强迫结束" 循环。
若 break 后面指定一个数值 n 的话,则"从里向外"打断第 n 个循环,
默认值为 break 1 ,也就是打断当前的循环。
在使用 break 时需要注意的是, 它与 return 及 exit 是不同的:
* break 是结束 loop
* return 是结束 function
* exit 是结束 script/shell
而 continue 则与 break 相反:强迫进入下一次循环动作。
若你理解不来的话,那你可简单的看成:在 continue 到 done 之间的句子略过而返回循环顶
端...
与 break 相同的是:continue 后面也可指定一个数值 n ,以决定继续哪一层(从里向外计算)
的循环,
默认值为 continue 1 ,也就是继续当前的循环。
在 shell script 设计中,若能善用 loop ,将能大幅度提高 script 在复杂条件下的处理能力。
请多加练习吧....
-----------
好了,该是到了结束的时候了。
婆婆妈妈的跟大家啰唆了一堆关于 shell 的基础概念,
目的不是要告诉大家"答案",而是要带给大家"启发"...
在日后关于 shell 的讨论中,我或许会经常用"链接"方式指引回来十三问中的内容,
以便我们在进行技术探讨时彼此能有一些讨论基础,而不至于各说各话、徒费时力。
但,更希望十三问能带给你更多的思考与乐趣,至为重要的是透过实作来加深理解。
是的,我很重视"实作"与"独立思考"这两项学习要素,若你能够掌握其中真义,那请容我说声:
--- 恭喜﹗十三问你没白看了﹗ ^_^
p.s.
至于补充问题部份,我暂时不写了。而是希望:
1) 大家扩充题目。
2) 一起来写心得。
Good luck and happy studying!
b1) [^ ] 跟 [! ] 差在哪?
这个问题等了好久都没人出来补充, 而我呢, 也被追杀了好几回... ^_^
趁着今晚有一点空闲, 赶快将此桩心事做一了结吧...
这道题目说穿了, 就是要探讨 Wildcard 与 Regular Expression 的差别的.
这也是许多初学 shell 的朋友很容易混乱的地方.
首先, 让我们回到十三问之第 2 问, 再一次将我们提到的 command line format 温习一次:
代码:
command_name options arguments
同时, 也再来理解一下我在第 5 问所提到的变量替换的特性:
代码:
先替换, 再重组 command lline!
有了这两道基础后, 才让我们来看看 wildcard 是甚么回事吧.
Part-I: Wildcard
首先, wildcard 也是属于 command line 的处理工序, 作用于 argument 里的 path 之上.
没错, 它不用在 command_name 也不用在 options 上.
而且, 若 argument 不是 path 的话, 那也与 wildcard 无关.
换句更为精确的定义来讲, wildcard 是一种命令行的路径扩展(path expansion)功能.
提到这个扩展, 那就不要忘记了 command line 的"重组"特性了!
是的, 这与变量替换(variable substitution)及命令替换(command substitution)的重组特
性是一样的!
也就是在 wildcard 进行扩展后, 命令行会先完成重组才会交给 shell 来处理.
了解了 wildcard 的扩展与重组特性后, 接下来, 让我们了解一些常见的 wildcard 吧:
*: 匹配 0 或多个字符
?: 匹配任意单一字符
[list]: 匹配 list 中的任意单一字符(注一)
[!list]: 匹配不在 list 中的任意单一字符
{string1,string2,...}: 匹配 sring1 或 string2 (或更多)其一字符串
(注一: list 可以为指定的个别字符, 如 abcd; 也可以为一段 ASCII 字符的起止范围, 如:
a-d .)
例:
a*b: a 与 b 之间可以有任意长度的任意字符, 也可以一个也没有, 如: aabcb, axyzb,
a012b, ab 等.
a?b: a 与 b 之间必须也只能有一个字符, 可以是任意字符, 如: aab, abb, acb, a0b 等.
a[xyz]b: a 与 b 之间必须也只能有一个字符, 但只能是 x 或 y 或 z, 如: axb, ayb, azb
这三个.
a[!0-9]b: a 与 b 之间必须也只能有一个字符, 但不能是阿拉伯数字, 如: axb, aab, a-b 等.
a{abc,xyz,123}b: a 与 b 之间只能是 abc 或 xyz 或 123 这三个字符串之一, 如
aabcb, axyzb, a123b 这三个.
注意:
1) [! ] 中的 ! 只有放在第一顺位时, 才有排除之功. 举例说:
[!a]* 表示当前目录下所有不以 a 开首的路径名称.
/tmp/[a\!]* 表示 /tmp 目录下以 a 或 ! 开首的路径名称. (思考: 为何 ! 前面要加 \ 呢?
提示: 十三问之 4 )
2) [ -] 中的 - 左右两边均有字符时, 才表示一段范围, 否则仅作 "-"(减号) 字符来处理. 举
例说:
/tmp/*[-z]/[a-zA-Z]* 表示 /tmp 目录下所有以 z 或 - 结尾的子目录下以英文字母(不分
大小写)开首的路径名称.
3) 以 * 或 ? 开首的 wildcard 不能匹配隐藏文件(即以 . 开首的文件). 举例说:
*.txt 并不能匹配 .txt 但可匹配 1.txt 这样的路径名称.
但 1*txt 及 1?txt 均可匹配 1.txt 这样的路径名称.
基本上, 要掌握 wildcard 并不难, 只要多加练习, 再勤于思考, 就能熟加运用了.
再次提醒: 别忘了"扩充+重组"这个重要特性, 而且只作用在 argument 的 path 上.
比方说, 假设当前目录下有 a.txt b.txt c.txt 1.txt 2.txt 3.txt 这几份文件.
当我们在命令行中下达 ls -l [0-9].txt 的命令行时,
因为 wildcard 处于 argument 的位置上, 于是根据其匹配的路径, 扩展为 1.txt 2.txt
3.txt ,
再重组出 ls -l 1.txt 2.txt 3.txt 这样的命令行.
因此, 你在命令行上敲 ls -l [0-9].txt 与 ls -l 1.txt 2.txt 3.txt 都是同样的结果, 其原因正
是于此了...
Part-II: Regular Expression
接下来的 Regular Expression(RE) 可是个大题目, 要讲的很多, 我这里当然不可能讲得很完
全.
只希望带给大家一个基本的入门概念, 就很是足够了...
先来考一下英文好了: What is expression?
简单来说, 就是"表达", 也就是人们在沟通时所要陈述的内容.
然而, 生活中, 表达方要清楚的将意思描述清楚而让接收方完整且无误的领会, 可不是件容易
的事情.
因而才会出现那么多的"误会", 真可叹句"表达不易"啊....
同样的情形也发生在计算机的数据处理过程中, 尤其是当我们在描术一段"文字内容"的时候...
那么, 我们不禁要问: 有何方法可以让大家的误会降至最低程度而让表达的精确度达到最高程
度呢?
答案就是"标准化"了, 亦就是我们这里要谈的 Regular Expression 啦.... ^_^
然而, 在进入 RE 介绍之前, 不防先让我们温习一下 shell 十三问第 4 问, 也就是关于
quoting 的部份.
关键是要能够区分 shell command line 上的 meta 与 literal 这两种不同的字符类别.
然后, 我这里才跟你讲:
--- RE 表达式里的字符也是分为 meta 与 literal 这两种!
呵, 不知亲爱的读者是否被我搞混乱了呢? ... ^_^
这也难怪啦, 因为这的确是最容易混乱的地方, 刚学 RE 的朋友很多时候都死在这里!
因此请特别小心理解哦...
简单而言, 除非你将 RE 写在特定程序使用的脚本里,
否则, 我们的 RE 也是透过 command line 输入的.
然而, 不少 RE 所始用的 meta 字符, 跟 shell meta 字符是冲突的.
比方说, * 这个字符, 在 RE 里是一个 modifier(后述), 在 command line 上, 却是个
wildcard !
那么, 我们该如何解决这样的冲突呢? 关键就是看你对十三问第 4 问所提的 quoting 是否够
理解了!
若你明白到 shell quoting 就是在 command line 上关闭 shell meta 这一基本原理,
那你就能很轻松的解决 RE meta 与 shell meta 的冲突问题了:
--- 用 shell quoting 关掉 shell meta 就是了!
就这么简单... ^_^
再以刚提到的 * 字符为例, 若在 command line 中没有 quoting 处理的话, 如 abc* ,
那就会被作为 wildcard expansion 来扩充及重组了.
若将之置于 quoting 中, 如 "abc*", 则可避免 wildcard expansion 的处理. (注一)
(* 注一:
是否需要 quoting 得视具体的程序及具体的命令语法而定. 以 grep 为例:
假设当前目录下有 abc 与 abc-xyz 这两份文件, 其中 abc 文件的内容就是 abc 这行文字.
当我们在 command line 输入 grep abc* abc* 的时候,
第一个 abc* 事实上并不需要 quoting 就可避免 wildcard 处理, 而第二个 abc* 则会被
扩展为 abc abc-xyz .
在命令行重组后, 将会成为 grep abc* abc abc-xyz . 其输出结果会是: abc:abc
然而, 若我们的 command line 改为 echo abc | grep abc* 的话, 若没 quoting 处理的
话, * 会被视为 wildcard ,
在命令行重组后, 将会成为 echo abc | grep abc abc-xyz . 将得不到任何输出结果.
要想得到结果的话, 那 command line 应改为: echo abc | grep "abc*"
)
好了, 说了大半天, 还没进入正式的 RE 介绍呢...
大家别急, 因为我的教学风格就是要先建立基础, 循序渐进的... ^_^
因此, 我这里还要在啰唆一个观念, 才会到 RE 的说明啦... (哈... 别打我....)
当我们在谈到 RE 时, 千万别跟 wildcard 搞混在一起!
尤其在 command line 的位置里, wildcard 只作用于 argument 的 path 上.
但是 RE 却只用于"字符串处理"的程序之中, 这与路径名称一点关系也没有!
RE 所处理的字符串通常是指纯文档或透过 stdin 读进的内容...
okay, 够了够了, 我已看到一堆人开始出现不大耐烦的样子了.... ^_^
现在, 就让我门登堂入室, 撩开 RE 的神秘面纱吧, 这样可以放过我了吧? 哈哈...
在 RE 的表达式里, 主要分两种字符(character): literal 与 meta.
所谓 literal 就是在 RE 里不具特殊功能的字符, 如 abc, 123 这些;
而 meta 在 RE 里具有特殊的功能, 要关闭之需在 meta 前面使用 escape( \ )字符.
然而, 在介绍 meta 之前, 先让我们来认识一下字符组合(character set)会更好些.
所谓的 char. set 就是将多个连续的字符作一个集合, 比方说:
abc: 表示 abc 三个连续的字符, 但彼此独立而非集合. (可简单视为三个 char. set)
(abc): 表示 abc 这三个连续字符的集合. (可简单视为一个 char. set)
abc|xyz: 表示或 abc 或 xyz 这两个 char. set 之一.
[abc]: 表示单一字符, 可为 a 或 b 或 c . (与 wildcard 之 [abc] 原理相同)
[^abc]: 表示单一字符, 不为 a 或 b 或 c 即可. (与 wildcard 之 [!abc] 原理相同)
. : 表示任意单一字符. (与 wildcard 之 ? 原理相同)
在认识了 char. set 这个概念后, 然后再让我们多认识几个 RE 中常见的 meta 字符:
- 锚点(anchor)
用以标识 RE 于句子中的位置所在. 常见有:
^: 表示句首. 如 ^abc 表示以 abc 开首的句子.
$: 表示句尾. 如 abc$ 表示以 abc 结尾的句子.
\<: 表示词首. 如 \<abc 表示以 abc 开首的词.
\>: 表示词尾. 如 abc\> 表示以 abc 结尾的词.
- 修饰字符(modifier)
独立表示时本身不具意义, 专门用以修改前一个 char. set 的出现次数. 常见有:
*: 表示前一个 char. set 的出现次数为 0 或多次. 如 ab*c 表示 a 与 c 之间可有 0 或多
个 b 存在.
?: 表示前一个 char. set 的出现次数为 0 或 1 次. 如 ab?c 表示 a 与 c 之间可有 0 或
1 个 b 存在.
+: 表示前一个 char. set 的出现次数为 1 或多次. 如 ab+c 表示 a 与 c 之间可有 1 或
多个 b 存在.
{n}: 表示前一个 char. set 的出现次数必须为 n 次. 如 ab{3,}c 表示 a 与 c 之间必须
有 3 个 b 存在.{n,}: 表示前一个 char. set 的出现次数至少为 n 次. 如 ab{3,}c 表示
a 与 c 之间至少有 3 个 b 存在.
{n,m}: 表示前一个 char. set 的出现次数为 n 到 m 次. 如 ab{3,5}c 表示 a 与 c 之
间有 3 到 5 个 b 存在.
然而, 当我们在识别 modifier 时, 却很容易忽略"边界(boundary)"字符的重要性.
以刚提到的 ab{3,5}c 为例, 这里的 a 与 c 就是边界字符了.
若没有边界字符的帮忙, 我们很容以作出错误的解读.
比方说: 我们用 ab{3,5} 这个 RE (少了 c 这个边界字符)可以抓到 abbbbbbbbbbc (a 后
有 10 个 b )这串字吗?
从刚才的 modifier 我们一般会认为我们要的 b 是 3 到 5 个, 若超出了此范围, 就不是我
们要表达的.
因此, 我们或会很轻率的认为这个 RE 抓不到结果...
然而答案却是可以的! 为甚么呢?
让我们重新解读 ab{3,5} 这个 RE 看看:
我们要表达的是 a 后接 3 到 5 个 b 即可, 但 3 到 5 个 b 后面我们却没规定是甚么,
因此在 RE 后面可以是任意的文字, 当然包括 b 也可以啦! (明白了吗?)
同样的, 我们用 b{3,5}c 也同样可以抓到 abbbbbbbbbbc 这串字的.
但我们若使用 ab{3,5}c 这样的 RE 时, 由于同时有 a 与 c 这两个边界字符, 那就截然不
同了!
有空再思考一下, 为何我们用下面这些 RE 都可抓到 abc 这串字呢?
x*
ax*, abx*, ax*b
abcx*, abx*c, ax*bc
bx*c, bcx*, x*bc
...(还有更多...)
但, 若我们在这些 RE 前后分别加一个 ^ 与 $ 这样的 anchor, 那又如何呢?
刚学 RE 时, 只要能掌握上面这些基本的 meta 大盖就可以入门了.
一如前述, RE 是一种规范化的文字表达方式, 主要用于某些文字处理工具之间,
如 grep, perl, vi, awk, sed, 等等. 常用以表示一段连续的字符串, 捕获之或替换之.
然而, 每种工具对 RE 表达式的具体解读或有一些细微差异, 不过, 基本原则还是一致的.
只要能掌握 RE 的基本原理, 那就一理通百理明了, 只是在实作时稍加变通即可.
比方以 grep 来说, 在 Linux 上你可找到 grep, egrep, fgrep 这几个程序, 其差异大致如
下:
* grep:
传统的 grep 程序, 在没有参数的情况下, 只输出符合 RE 字符串之句子. 常见参数如下:
-v: 逆反模示, 只输出"不含" RE 字符串之句子.
-r: 递归模式, 可同时处理所有层级子目录里的文件.
-q: 静默模式, 不输出任何结果(stderr 除外. 常用以获取 return value, 符合为 true, 否则
为 false .)
-i: 忽略大小写.
-w: 整词比对, 类似 \<word\> .
-n: 同时输出行号.
-c: 只输出符合比对的行数.
-l: 只输出符合比对的文件名称.
-o: 只输出符合 RE 的字符串. (gnu 新版独有, 不见得所有版本都支持.)
-E: 切换为 egrep .
* egrep:
为 grep 的扩充版本, 改良了许多传统 grep 不能或不便的操作. 比方说:
- grep 之下不支持 ? 与 + 这两种 modifier, 但 egrep 则可.
- grep 不支持 a|b 或 (abc|xyz) 这类"或一"比对, 但 egrep 则可.
- grep 在处理 {n,m} 时, 需用 \{ 与 \} 处理, 但 egrep 则不需.
诸如此类的... 我个人会建议能用 egrep 就不用 grep 啦... ^_^
* fgrep:
不作 RE 处理, 表达式仅作一般字符串处理, 所有 meta 均失去功能.
好了...
关于 RE 的入门, 我暂时就介绍到这里.
虽然写得有点乱, 且有些观念也不很精确, 不过, 姑且算是对大家有一个交差吧.... ^_^
若这两天还有时间的话, 我再举些范例来分析一下, 以助大家更好的理解.
假如更有可能的话, 也顺道为大家介绍一下 sed 这个工具.
(啊, 这次我不敢作保证了哦... ^_^ )
----------------
(顺道一提: eval )
讲到 command line 的重组特性, 真的需要我们好好的加以理解的.
如此便能抽丝剥襺的一层层的将正个 command line 分析得一清二楚, 而不至于含糊.
假如这个重组特性理解下来来, 那么, 接下来我们介绍一个好玩的命令 --- eval .
我们在不少变量替换的过程中, 常碰到所谓的复式变量的问题, 如:
代码:
a=1
A1=abc
我们都知道 echo $A1 就可得到 abc 这个结果.
然而, 我们能否用 $A$a 来取代 $A1 而同样替换出 abc 呢?
这个问题我们可用很轻松的用 eval 来解决:
代码:
eval echo \$A$a
说穿了, eval 只不过是在命令行完成替换重组后, 再来一次替换重组罢了...
就是这么简单啦~~~ ^_^

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|京峰教育,只为有梦想的人 ( 京ICP备15013173号 )

GMT+8, 2021-1-26 22:33 , Processed in 0.024964 second(s), 12 queries , Apc On.

快速回复 返回顶部 返回列表