资讯专栏INFORMATION COLUMN

【转】shell十三问,为linux学习打基础(下)

Scliang / 1128人阅读

摘要:本文整理并转自上的帖子学习共享十三問,此贴是年发表的,但却是相当不错的基础知识汇集贴,原帖主使用的台湾风格,本文加以简体化和整理。首先,是同时写入的问题。凡是有经验的操作者,都会尽量避免或降低的频率。

本文整理并转自CU上的帖子[学习共享] shell 十三問?,此贴是2003年发表的,但却是相当不错的linux基础知识汇集贴,原帖主使用的台湾风格,本文加以简体化和整理。

第11问:> 與 < 差在哪? 11.1

谈到I/O redirection,不妨先让我们认识一下File Descriptor(FD)。程序的运算,在大部份情况下都是进行数据(data)的处理,这些数据从哪读进?又,送出到哪里呢?这就是File descriptor(FD)的功用了。

在shell程序中,最常使用的FD大概有三个,分别为:

0: Standard Input(STDIN)

1: Standard Output(STDOUT)

2: Standard Error Output(STDERR)

在标准情况下,这些FD分别跟如下设备(device)关联:

stdin(0):keyboard

stdout(1):monitor

stderr(2):monitor

我们可以用如下下命令测试一下:

$ mail -s test root
this is a test mail.
please skip.
^d(同时按crtl跟d键)

很明显,mail程序所读进的数据,就是从stdin也就是keyboard读进的。不过,不见得每个程序的stdin都跟mail一样从keyboard读进,因为程序作者可以从档案参数读进stdin,如:

$ cat /etc/passwd

但,要是cat之后没有档案参数则又如何呢?哦,请您自己玩玩看啰….^_^

$ cat

(请留意数据输出到哪里去了,最后别忘了按^d离开…)至于stdout与stderr,嗯…等我有空再续吧…^_^ 还是,有哪位前辈要来玩接龙呢?

11.2

沿文再续,书接上一回…^_^ 相信,经过上一个练习后,你对stdinstdout应该不难理解吧?然后,让我们继续看stderr好了。

事实上,stderr没甚么难理解的:说穿了就是“错误信息”要往哪边送而已…比方说,若读进的档案参数是不存在的,那我们在monitor上就看到了:

$ ls no.such.file
ls: no.such.file: No such file or directory

若,一个命令同时产生stdout与stderr呢?那还不简单,都送到monitor来就好了:

$ touch my.file
$ ls my.file no.such.file
ls: no.such.file: No such file or directory
my.file

okay,至此,关于FD及其名称、还有相关联的设备,相信你已经没问题了吧?那好,接下来让我们看看如何改变这些FD的预设数据信道,我们可用<来改变读进的数据信道(stdin),使之从指定的档案读进。我们可用>来改变送出的数据信道(stdout,stderr),使之输出到指定的档案。比方说:

$ cat < my.file

就是从my.file读进数据

$ mail -s test root < /etc/passwd

则是从/etc/passwd读进…

这样一来,stdin将不再是从keyboard读进,而是从档案读进了…严格来说,<符号之前需要指定一个FD的(之间不能有空白),但因为0<的预设值,因此<0<是一样的!okay,这个好理解吧?

那,要是用两个<<又是啥呢?这是所谓的HERE Document,它可以让我们输入一段文本,直到读到<<后指定的字串。比方说:

$ cat <

这样的话,cat会读进3行句子,而无需从keyboard读进数据且要等^d结束输入。至于>又如何呢?且听下回分解...

11.3

okay,又到讲古时间~~~当你搞懂了0<原来就是改变stdin的数据输入信道之后,相信要理解如下两个redirection就不难了:1> 2> 。前者是改变stdout的数据输出信道,后者是改变stderr的数据输出信道。两者都是将原本要送出到monitor的数据转向输出到指定档案去。

由于1>的预设值,因此,1>>是相同的,都是改变stdout。用上次的ls例子来说明一下好了:

$ ls my.file no.such.file 1>file.out
ls: no.such.file: No such file or directory

这样monitor就只剩下stderr而已。因为stdout给写进file.out去了。

$ ls my.file no.such.file 2>file.err
my.file

这样monitor就只剩下stdout,因为stderr写进了file.err

$ ls my.file no.such.file 1>file.out 2>file.err

这样monitor就啥也没有,因为stdout与stderr都给转到档案去了…

呵~~~看来要理解>一点也不难啦﹗是不?没骗你吧?^_^ 不过,有些地方还是要注意一下的。
首先,是同时写入的问题。比方如下这个例子:

$ ls my.file no.such.file 1>file.both 2>file.both

假如stdout(1)与stderr(2)都同时在写入file.both的话,则是采取“覆盖”方式:后来写入的覆盖前面的。让我们假设一个stdout与stderr同时写入file.out的情形好了:

首先stdout写入10个字元

然后stderr写入6个字元

那么,这时候原本stdout输出的10个字元就被stderr覆盖掉了。那,如何解决呢?所谓山不转路转、路不转人转嘛,我们可以换一个思维:将stderr导进stdout或将stdout导进sterr,而不是大家在抢同一份档案,不就行了﹗bingo﹗就是这样啦:

2>&1就是将stderr并进stdout作输出

1>&2>&2就是将stdout并进stderr作输出

于是,前面的错误操作可以改为:

$ ls my.file no.such.file 1>file.both 2>&1
或
$ ls my.file no.such.file 2>file.both >&2

这样,不就皆大欢喜了吗?呵~~~ ^_^ 不过,光解决了同时写入的问题还不够,我们还有其他技巧需要了解的。故事还没结束,别走开﹗广告后,我们再回来…﹗

11.4

okay,这次不讲I/O Redirction,讲佛吧…(有没搞错?﹗网中人是否头壳烧坏了?…)嘻~~~ ^_^

学佛的最高境界,就是“四大皆空”。至于是空哪四大块?我也不知,因为我还没到那境界…但这个“空”字,却非常值得我们返复把玩的:---色即是空、空即是色﹗好了,施主要是能够领会“空”的禅意,那离修成正果不远矣~~~

在Linux档案系统里,有个设备档位于/dev/null。许多人都问过我那是甚么玩意儿?我跟你说好了:那就是“空”啦﹗没错﹗空空如也的空就是null了….请问施主是否忽然有所顿误了呢?然则恭喜了~~~ ^_^ 这个null在I/O Redirection中可有用得很呢:

若将FD1FD2转到/dev/null去,就可将stdout与stderr弄不见掉。

若将FD0接到/dev/null来,那就是读进nothing。

比方说,当我们在执行一个程序时,画面会同时送出stdout跟stderr,
假如你不想看到stderr(也不想存到档案去),那可以:

$ ls my.file no.such.file 2>/dev/null
my.file

若要相反:只想看到stderr呢?还不简单﹗将stdout弄到null就行:

$ ls my.file no.such.file >/dev/null
ls: no.such.file: No such file or directory

那接下来,假如单纯只跑程序,不想看到任何输出结果呢?哦,这里留了一手上次节目没讲的法子,专门赠予有缘人﹗…^_^ 除了用>/dev/null 2>&1之外,你还可以如此:

$ ls my.file no.such.file &>/dev/null

(提示:将&>换成>&也行啦~~!)

okay?讲完佛,接下来,再让我们看看如下情况:

$ echo "1" > file.out
$ cat file.out
1
$ echo "2" > file.out
$ cat file.out
2

看来,我们在重导stdout或stderr进一份档案时,似乎永远只获得最后一次导入的结果。那,之前的内容呢?呵~~~要解决这个问提很简单啦,将>换成>>就好:

$ echo "3" >> file.out
$ cat file.out
2
3

如此一来,被重导的目标档案之内容并不会失去,而新的内容则一直增加在最后面去。easy?呵…^_^

但,只要你再一次用回单一的>来重导的话,那么,旧的内容还是会被“洗”掉的﹗这时,你要如何避免呢?----备份﹗yes,我听到了﹗不过….还有更好的吗?既然与施主这么有缘份,老纳就送你一个锦囊妙法吧:

$ set -o noclobber
$ echo "4" > file.out
-bash: file: cannot overwrite existing file

那,要如何取消这个“限制”呢?哦,将set -o换成set +o就行:

$ set +o noclobber
$ echo "5" > file.out
$ cat file.out
5

再问:那…有办法不取消而又“临时”盖写目标档案吗?哦,佛曰:不可告也﹗啊~开玩笑的、开玩笑的啦~ ^_^唉,早就料到人心是不足的了﹗

$ set -o noclobber
$ echo "6" >| file.out
$ cat file.out
6

留意到没有:在>后面再加个“|”就好(注意:>与|之间不能有空白哦)… 呼…(深呼吸吐纳一下吧)~~~ ^_^再来还有一个难题要你去参透的呢:

$ echo "some text here" > file
$ cat < file
some text here
$ cat < file > file.bak
$ cat < file.bak
some text here
$ cat < file > file
$ cat < file

嗯?!注意到没有?!!----怎么最后那个cat命令看到的file竟是空的?﹗why?why?why?同学们:下节课不要迟到啰~~~!

11.5

当当当~上课啰~ ^_^
前面提到:$ cat < file > file 之后原本有内容的档案结果却被洗掉了﹗要理解这一现像其实不难,这只是priority的问题而已:

在IO Redirection中,stdout与stderr的管道会先准备好,才会从stdin读进数据。也就是说,在上例中,> file会先将file清空,然后才读进< file,但这时候档案已经被清空了,因此就变成读不进任何数据了…哦~原来如此~~ ^_^

那…如下两例又如何呢?

$ cat <> file
$ cat < file >> file

嗯…同学们,这两个答案就当练习题啰,下节课之前请交作业﹗好了,I/O Redirection也快讲完了,sorry,因为我也只知道这么多而已啦~嘻 ^_^ 不过,还有一样东东是一定要讲的,各位观众(请自行配乐~!#@!$%):----就是pipe line也!

谈到pipe line,我相信不少人都不会陌生:我们在很多command line上常看到的“|”符号就是pipe line了。不过,究竟pipe line是甚么东东呢?别急别急…先查一下英汉字典,看看pipe是甚么意思?没错﹗它就是“水管”的意思…那么,你能想像一下水管是怎么一根接着一根的吗?又,每根水管之间的input跟output又如何呢?嗯??灵光一闪:原来pipe line的I/O跟水管的I/O是一模一样的:上一个命令的stdout接到下一个命令的stdin去了!的确如此…不管在command line上你使用了多少个pipe line,前后两个command的I/O都是彼此连接的﹗(恭喜:你终于开窍了﹗^_^)

不过…然而…但是……stderr呢?好问题﹗不过也容易理解:若水管漏水怎么办?也就是说:在pipe line之间,前一个命令的stderr是不会接进下一命令的stdin的,其输出,若不用2>导到file去的话,它还是送到摄像头上面来﹗这点请你在pipe line运用上务必要注意的。那,或许你又会问:有办法将stderr也喂进下一个命令的stdin去吗?(贪得无厌的家伙﹗)方法当然是有,而且你早已学过了﹗^_^ 我提示一下就好:请问你如何将stderr合并进stdout一同输出呢?若你答不出来,下课之后再来问我吧…(如果你脸皮真够厚的话…)

或许,你仍意尤未尽﹗或许,你曾经碰到过下面的问题:

cm1 | cm2 | cm3…这段pipe line中,若要将cm2的结果存到某一档案呢?

若你写成cm1 | cm2 > file | cm3的话,那你肯定会发现cm3的stdin是空的﹗(当然啦,你都将水管接到别的水池了﹗)聪明的你或许会如此解决:cm1 | cm2 > file; cm3 < file 是的,你的确可以这样做,但最大的坏处是:这样一来,file I/O会变双倍﹗在command执行的整个过程中,file I/O是最常见的最大性能杀手。凡是有经验的shell操作者,都会尽量避免或降低file I/O的频率。那,上面问题还有更好方法吗?有的,那就是tee命令了。

所谓tee命令是在不影响原本I/O的情况下,将stdout复制一份到档案去。因此,上面的命令行可以如此打:

cm1 | cm2 | tee file | cm3

在预设上,tee会改写目标档案,若你要改为增加内容的话,那可用-a参数达成。
基本上,pipe line的应用在shell操作上是非常广泛的,尤其是在text filtering方面,
凡举catmoreheadtailwcexpandtrgrepsedawk,…等等文字处理工具,搭配起pipe line来使用,你会惊觉command line原来是活得如此精彩的﹗常让人有“众里寻他千百度,蓦然回首,那人却在灯火阑珊处﹗”之感…^_^

好了,关于I/O Redirection的介绍就到此告一段落。若日后有空的话,再为大家介绍其它在shell上好玩的东西﹗bye…^_^

第12问:你要 if 還是 case 呢?

放了一个愉快的春节假期,人也变得懒懒散散的…只是,答应了大家的作业,还是要坚持完成就是了~~~

还记得我们在第10章所介绍的return value吗?是的,接下来介绍的内容与之有关,若你的记忆也被假期的欢乐时光所抵消掉的话,那,建议您还是先回去温习温习再回来…

若你记得return value,我想你也应该记得了&&||是甚么意思吧?用这两个符号再配搭command group的话,我们可让shell script变得更加聪明哦。比方说:

comd1 && {
    comd2
    comd3
    :
} || {
    comd4
    comd5
}

意思是说:假如comd1的return value为true的话,然则执行comd2与comd3,否则执行comd4与comd5。

事实上,我们在写shell script的时候,经常需要用到这样那样的条件以作出不同的处理动作。
&&||的确可以达成条件执行的效果,然而,从“人类语言”上来理解,却不是那么直观。
更多时候,我们还是喜欢用if….then…else…这样的keyword来表达条件执行。在bash shell中,我们可以如此修改上一段代码:

if comd1
then
    comd2
    comd3
else
    comd4
    comd5
fi

这也是我们在shell script中最常用到的if判断式:只要if后面的command line返回true的return value(我们最常用test命令来送出return value),然则就执行then后面的命令,否则执行else后的命令;fi则是用来结束判断式的keyword。

在if判断式中,else部份可以不用,但then是必需的。(若then后不想跑任何command,可用:这个null command代替)。当然,then或else后面,也可以再使用更进一层的条件判断式,这在shell script设计上很常见。

若有多项条件需要“依序”进行判断的话,那我们则可使用elif这样的keyword:

if comd1; then
    comd2
elif comd3; then
    comd4
else
    comd5
fi

意思是说:若comd1为true,然则执行comd2;否则再测试comd3,然则执行comd4;倘若comd1与comd3均不成立,那就执行comd5。

if判断式的例子很常见,你可从很多shell script中看得到,我这里就不再举例子了…接下来要为大家介绍的是case判断式。

虽然if判断式已可应付大部份的条件执行了,然而,在某些场合中,却不够灵活,尤其是在string式样的判断上,比方如下:

QQ() {
    echo -n "Do you want to continue?(Yes/No):"
    read YN
    if [ "$YN" = Y -o "$YN" = y -o "$YN" = "Yes" -o "$YN" = "yes" -o "$YN" = "YES" ]
    then
        QQ
    else
        exit 0
    fi
}
QQ

从例中,我们看得出来,最麻烦的部份是在于判断YN的值可能有好几种式样。聪明的你或许会如此修改:

if echo "$YN" | grep -q "^[Yy]([Ee][Ss])*$"

也就是用Regular Expression来简化代码。(我们有机会再来介绍RE)只是…是否有其它更方便的方法呢?有的,就是用case判断式即可:

QQ() {
    echo -n "Do you want to continue?(Yes/No):"
    read YN
    case "$YN" in
        [Yy]|[Yy][Ee][Ss])
            QQ
            ;;
        *)
            exit 0
            ;;
    esac
}
QQ

我们常用case的判断式来判断某一变量在不同的值(通常是string)时作出不同的处理,比方说,判断script参数以执行不同的命令。若你有兴趣、且用Linux系统的话,不妨挖一挖/etc/init.d/*里那堆script中的case用法。如下就是一例:

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        rhstatus
        ;;
    restart|reload)
        restart
        ;;
    condrestart)
        [ -f /var/lock/subsys/syslog ] && restart || :
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart}"
        exit 1
    esac

(若你对positional parameter的印像已经模糊了,请重看第9章吧。)okay,十三问还剩一问而已,过几天再来搞定之….^_^

第13问: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

上例的执行结果将会是:

for会定义一个叫var的变量,其值依次是one two three four five。

因为有5个变量值,因此do与done之间的命令行会被循环执行5次。

每次循环均用echo产生三行句子。而第二行中不在hard quote之内的$var会依次被替换为one two three four five。

当最后一个变量值处理完毕,循环结束。

我们不难看出,在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,则执行dodone之间的命令,然后重新判断while后的return value。

若为false,则不再执行do与done之间的命令而结束循环。

分析上例:

在while之前,定义变量num=1。

然后测试(test)$num是否小于或等于10。

结果为true,于是执行echo并将num的值加一。

再作第二轮测试,此时num的值为1+1=2,依然小于或等于10,因此为true,继续循环。

直到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相反:强迫进入下一次循环动作。若你理解不来的话,那你可简单的看成:在continuedone之间的句子略过而返回循环顶端…与break相同的是:continue后面也可指定一个数值n,以决定继续哪一层(从里向外计算)的循环,预设值为continue 1,也就是继续当前的循环。
在shell script设计中,若能善用loop,将能大幅度提高script在复杂条件下的处理能力。请多加练习吧….

好了,该是到了结束的时候了。

婆婆妈妈的跟大家罗唆了一堆关于shell的基础概念,目的不是要告诉大家“答案”,而是要带给大家“启发”…在日后关于shell的讨论中,我或许会经常用“连接”方式指引回来十三问中的内容,以便我们在进行技术探讨时彼此能有一些讨论基础,而不至于各说各话、徒费时力。但,更希望十三问能带给你更多的思考与乐趣,至为重要的是透过实作来加深理解。

是的,我很重视“实作”与“独立思考”这两项学习要素,若你能够掌握其中真义,那请容我说声:
---恭喜﹗十三问你没白看了﹗^_^

p.s.

至于补充问题部份,我暂时不写了。而是希望:

大家扩充题目。

一起来写心得。

Good luck and happy studying!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/15241.html

相关文章

  • shell三问linux学习基础

    摘要:本文整理并转自上的帖子学习共享十三問,此贴是年发表的,但却是相当不错的基础知识汇集贴,原帖主使用的台湾风格,本文加以简体化和整理。首先,是同时写入的问题。凡是有经验的操作者,都会尽量避免或降低的频率。 本文整理并转自CU上的帖子[学习共享] shell 十三問?,此贴是2003年发表的,但却是相当不错的linux基础知识汇集贴,原帖主使用的台湾风格,本文加以简体化和整理。 第11问:>...

    caikeal 评论0 收藏0
  • shell三问linux学习基础(上)

    摘要:事实上,在日后的操作及设计上,命令是最常被使用的命令之一。重导向,或将命令置于背境执行。在前一个命令结束时,而忽略其返回值,继续执行下一个命令。空白键已被关掉,仅作为空白键处理。 本文整理并转自CU上的帖子[学习共享] shell 十三問?,此贴是2003年发表的,但却是相当不错的linux基础知识汇集贴,原帖主使用的台湾风格,本文加以简体化和整理。 第1问:为何叫做shell? 在介...

    glumes 评论0 收藏0
  • shell三问linux学习基础(上)

    摘要:事实上,在日后的操作及设计上,命令是最常被使用的命令之一。重导向,或将命令置于背境执行。在前一个命令结束时,而忽略其返回值,继续执行下一个命令。空白键已被关掉,仅作为空白键处理。 本文整理并转自CU上的帖子[学习共享] shell 十三問?,此贴是2003年发表的,但却是相当不错的linux基础知识汇集贴,原帖主使用的台湾风格,本文加以简体化和整理。 第1问:为何叫做shell? 在介...

    ShevaKuilin 评论0 收藏0
  • AI开发书籍分享

    摘要:编程书籍的整理和收集最近一直在学习深度学习和机器学习的东西,发现深入地去学习就需要不断的去提高自己算法和高数的能力然后也找了很多的书和文章,随着不断的学习,也整理了下自己的学习笔记准备分享出来给大家后续的文章和总结会继续分享,先分享一部分的 编程书籍的整理和收集 最近一直在学习deep learning深度学习和机器学习的东西,发现深入地去学习就需要不断的去提高自己算法和高数的能力然后...

    huayeluoliuhen 评论0 收藏0
  • AI开发书籍分享

    摘要:编程书籍的整理和收集最近一直在学习深度学习和机器学习的东西,发现深入地去学习就需要不断的去提高自己算法和高数的能力然后也找了很多的书和文章,随着不断的学习,也整理了下自己的学习笔记准备分享出来给大家后续的文章和总结会继续分享,先分享一部分的 编程书籍的整理和收集 最近一直在学习deep learning深度学习和机器学习的东西,发现深入地去学习就需要不断的去提高自己算法和高数的能力然后...

    antz 评论0 收藏0

发表评论

0条评论

Scliang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<