shell十三问,为linux学习打基础(二)

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

第6问:exec跟source差在哪?

这次先让我们从CU Shell版的一个实例贴子来谈起吧:(论坛改版后原连接已经失效)例中的提问原文如下:

cd /etc/aa/bb/cc 可以执行,但是把这条命令写入shell时shell不执行!这是什么原因呀!(意思是:运行脚本后并没有移到/etc/aa/bb/cc目录)

我当时如何回答暂时别去深究,先让我们了解一下行程(process)的观念好了。首先,我们所执行的任何程序,都是由父行程(parent process)所产生出来的一个子行程(child process),子行程在结束后,将返回到父行程去。此一现像在Linux系统中被称为fork。(为何要程为fork呢?嗯,画一下图或许比较好理解…^_^)

当子行程被产生的时候,将会从父行程那里获得一定的资源分配、及(更重要的是)继承父行程的环境﹗让我们回到上一章所谈到的“环境变量”吧:

所谓环境变量其实就是那些会传给子行程的变量。简单而言,“遗传性”就是区分本地变量与环境变量的决定性指标。

然而,从遗传的角度来看,我们也不难发现环境变量的另一个重要特征:

环境变量只能从父行程到子行程单向继承。换句话说:在子行程中的环境如何变更,均不会影响父行程的环境。

接下来,再让我们了解一下命令脚本(shell script)的概念。所谓的shell script讲起来很简单,就是将你平时在shell prompt后所输入的多行command line依序写入一个文件去而已。其中再加上一些条件判断、互动界面、参数运用、函数调用等等技巧,得以让script更加“聪明”的执行,但若撇开这些技巧不谈,我们真的可以简单的看成script只不过依次执行预先写好的命令行而已。

再结合以上两个概念(process + script),那应该就不难理解如下这句话的意思了:

正常来说,当我们执行一个shell script时,其实是先产生一个sub-shell的子行程,然后sub-shell再去产生命令行的子行程。

然则,那让我们回到本章开始时所提到的例子再从新思考:cd /etc/aa/bb/cc可以执行,但是把这条命令写入shell时shell不执行!这是什么原因呀!我当时的答案是这样的:

因为,一般我们跑的shell script是用subshell去执行的。从process的观念来看,是parent process产生一个child process去执行,当child结束后,会返回parent,但parent的环境是不会因child的改变而改变的。所谓的环境元数很多,凡举effective id,variable,workding dir等等…其中的workding dir(PWD)正是楼主的疑问所在:当用subshell来跑script的话,subshell的PWD会因为cd而变更,但当返回primary shell时,$PWD是不会变更的。

能够了解问题的原因及其原理是很好的,但是?如何解决问题恐怕是我们更感兴趣的﹗是吧?^_^

那好,接下来,再让我们了解一下source命令好了。当你有了fork的概念之后,要理解source就不难:

所谓source就是让script在当前shell内执行、而不是产生一个sub-shell来执行。

由于所有执行结果均于当前shell内完成,若script的环境有所改变,当然也会改变当前环境了﹗因此,只要我们将原本单独输入的script命令行变成source命令的参数,就可轻易解决前例提到的问题了。

比方说,原本我们是如此执行script的:

./my.script

现在改成这样即可:

source ./my.script

或:

../my.script

说到这里,我想,各位有兴趣看看/etc底下的众多设定文件,应该不难理解它们被定义后,如何让其他script读取并继承了吧?若然,日后你有机会写自己的script,应也不难专门指定一个设定文件以供不同的script一起“共享”了…^_^

okay,到这里,若你搞得懂fork与source的不同,那接下来再接受一个挑战:

exec又与source/fork有何不同呢?

哦…要了解exec或许较为复杂,尤其扯上File Descriptor的话…不过,简单来说:

exec也是让script在同一个行程上执行,但是原有行程则被结束了。也就是简而言之:原有行程会否终止,就是exec与source/fork的最大差异了。

嗯,光是从理论去理解,或许没那么好消化,不如动手“实作+思考”来的印象深刻哦。下面让我们写两个简单的script,分别命名为1.sh及2.sh:

1.sh

#!/bin/bashA=B
echo "PID for 1.sh before exec/source/fork:$$"export A
echo "1.sh: \$A is $A"case $1 in
    exec)
        echo "using exec…"
        exec ./2.sh;;
    source)
        echo "using source…"
        ../2.sh;;
    *)
        echo "using fork by default…"
        ./2.sh;;
esac
echo "PID for 1.sh after exec/source/fork:$$"echo "1.sh: \$A is $A"

2.sh

#!/bin/bashecho "PID for 2.sh: $$"echo "2.sh get \$A=$A from 1.sh"A=C
export A
echo "2.sh: \$A is $A"

然后,分别跑如下参数来观察结果:

$ ./1.sh fork
$ ./1.sh source
$ ./1.sh exec

好了,别忘了仔细比较输出结果的不同及背后的原因哦…若有疑问,欢迎提出来一起讨论讨论~~~happy scripting!^_^

第7问:( ) 与 { } 差在哪?

嗯,这次轻松一下,不讲太多…^_^

先说一下,为何要用()或{ }好了。许多时候,我们在shell操作上,需要在一定条件下一次执行多个命令,也就是说,要么不执行,要么就全执行,而不是每次依序的判断是否要执行下一个命令。或是,需要从一些命令执行优先次顺中得到豁免,如算术的2*(3+4)那样…

这时候,我们就可引入“命令群组”(command group)的概念:将多个命令集中处理。在shell command line中,一般人或许不太计较()与{ }这两对符号的差异,虽然两者都可将多个命令作群组化处理,但若从技术细节上,却是很不一样的:

  • ( ) 将command group置于sub-shell去执行,也称nested sub-shell。
  • { } 则是在同一个shell内完成,也称为non-named command group。

若,你对上一章的fork与source的概念还记得了的话,那就不难理解两者的差异了。

要是在command group中扯上变量及其他环境的修改,我们可以根据不同的需求来使用()或{ }。

通常而言,若所作的修改是临时的,且不想影响原有或以后的设定,那我们就nested sub-shell,反之,则用non-named command group。

是的,光从command line来看,()与{ }的差别就讲完了,够轻松吧~~~ ^_^

然而,若这两个meta用在其他command meta或领域中(如Regular Expression),还是有很多差别的。只是,我不打算再去说明了,留给读者自己慢慢发掘好了…我这里只想补充一个概念,就是function

所谓的function,就是用一个名字去命名一个command group,然后再调用这个名字去执行command group。从non-named command group来推断,大概你也可以猜到我要说的是{ }了吧?(yes!你真聪明﹗^_^)

在bash中,function的定义方式有两种: 方式一:

function function_name {
command1
command2
command3
…
}

方式二:

fuction_name() {
command1
command2
command3
…
}

用哪一种方式无所谓,只是若碰到所定意的名称与现有的命令或别名(Alias)冲突的话,方式二或许会失败。但方式二起码可以少打function这一串英文字母,对懒人来说(如我),又何乐不为呢?…^_^

function在某一程度来说,也可称为“函式”,但请不要与传统编程所使用的函式(library)搞混了,毕竟两者差异很大。惟一相同的是,我们都可以随时用“已定义的名称”来调用它们…

若我们在shell操作中,需要不断的重覆执行某些命令,我们首先想到的,或许是将命令写成命令脚本(shell script)。不过,我们也可以写成function,然后在command line中打上function_name就可当一舨的script来使用了。

只是若你在shell中定义的function,除了可用unset function_name取消外,一旦退出shell,function也跟着取消。

然而,在script中使用function却有许多好处,除了可以提高整体script的执行性能外(因为已被加载),还可以节省许多重覆的代码…

简单而言,若你会将多个命令写成script以供调用的话,那,你可以将function看成是script中的script…^_^

而且,透过上一章介绍的source命令,我们可以自行定义许许多多好用的function,再集中写在特定文件中,然后,在其他的script中用source将它们加载并反复执行。

若你是RedHat Linux的使用者,或许,已经猜得出/etc/rc.d/init.d/functions这个文件是作啥用的了~~~ ^_^

okay,说要轻松点的嘛,那这次就暂时写到这吧。祝大家学习愉快﹗^_^

第8问:(())与( ) 還有${ } 差在哪?

我们上一章介绍了( )与{ }的不同,这次让我们扩展一下,看看更多的变化:()与{ }又是啥玩意儿呢?

在bash shell中,$()与``(反引号)都是用来做命令替换用(command substitution)的。所谓的命令替换与我们第五章学过的变量替换差不多,都是用来重组命令行:

完成引号里的命令行,然后将其结果替换出来,再重组命令行。

例如:

$ echo the last sunday is $(date -d "last sunday" +%Y-%m-%d)

如此便可方便得到上一星期天的日期了…^_^

在操作上,用$()或``都无所谓,只是我”个人”比较喜欢用$(),理由是:

  • 1.``很容易与’ ‘(单引号)搞混乱,尤其对初学者来说。有时在一些奇怪的字形显示中,两种符号是一模一样的(直竖两点)。当然了,有经验的朋友还是一眼就能分辩两者。只是,若能更好的避免混乱,又何乐不为呢?^_^
  • 2.在多层次的复合替换中, 须要额外的跳脱(`)处理,而$() 则比较直观。例如:这是错的:
command1 `command2 `command3` `

原本的意图是要在command2 command3先将command3提换出来给command2处理,然后再将结果传给command1 command2…来处理。然而,真正的结果在命令行中却是分成了command2与``两段。正确的输入应该如下:

command1 `command2 \`command3\` `

要不然,换成$()就没问题了:

command1 $(command2 $(command3))

只要你喜欢,做多少层的替换都没问题啦~~~ ^_^

不过,$()并不是没有斃端的…

首先,`基本上可用在全部的unix shell中使用,若写成shell script,其移植性比较高。而$()并不见的每一种shell都能使用,我只能跟你说,若你用bash2`的话,肯定没问题…^_^

接下来,再让我们看${}吧…它其实就是用来作变量替换用的啦。一般情况下,$var${var}并没有啥不一样。但是用${}会比较精确的界定变量名称的范围,比方说:

$ A=B
$ echo $AB

原本是打算先将$A的结果替换出来,然后再补一个B字母于其后,但在命令行上,真正的结果却只是会提换变量名称为AB的值出来…若使用${}就没问题了:

$ echo ${A}B
BB

不过,假如你只看到${}只能用来界定变量名称的话,那你就实在太小看bash了﹗为了完整起见,我这里再用一些例子加以说明${}的一些特异功能。

假设我们定义了一个变量为:

file=/dir1/dir2/dir3/my.file.txt

我们可以用${}分别替换获得不同的值:

${file#*/} # 拿掉第一条/及其左边的字串:dir1/dir2/dir3/my.file.txt
${file##*/} # 拿掉最后一条/及其左边的字串:my.file.txt
${file#*.} # 拿掉第一个.及其左边的字串:file.txt
${file##*.} # 拿掉最后一个.及其左边的字串:txt
${file%/*} # 拿掉最后条/及其右边的字串:/dir1/dir2/dir3
${file%%/*} # 拿掉第一条/及其右边的字串:(空值)
${file%.*} # 拿掉最后一个.及其右边的字串:/dir1/dir2/dir3/my.file
${file%%.*} #拿掉第一个.及其右边的字串:/dir1/dir2/dir3/my

记忆的方法为:

# 是去掉左边(在键盘上#在$之左边)
% 是去掉右边(在键盘上%在$之右边)
单一符号是最小匹配,两个符号是最大匹配。
${file:0:5} # 提取最左边的5个字节:/dir1
${file:5:5} # 提取第5个字节右边的连续5个字节:/dir2

我们也可以对变量值里的字串作替换:

${file/dir/path} # 将第一个dir提换为path:/path1/dir2/dir3/my.file.txt
${file//dir/path} # 将全部dir提换为path:/path1/path2/path3/my.file.txt

利用${}还可针对不同的变数状态赋值(没设定、空值、非空值)

${file-my.file.txt}    # 假如$file没有设定,则使用my.file.txt作传回值。(空值及非空值时不作处理)
${file:-my.file.txt}   # 假如$file没有设定或为空值,则使用my.file.txt作传回值。(非空值时不作处理)
${file+my.file.txt}    # 假如$file设为空值或非空值,均使用my.file.txt作传回值。(没设定时不作处理)
${file:+my.file.txt}   # 若$file为非空值,则使用my.file.txt作传回值。(没设定及空值时不作处理)
${file=my.file.txt}    # 若$file没设定,则使用my.file.txt作传回值,同时将$file赋值为my.file.txt。(空值及非空值时不作处理)
${file:=my.file.txt}   # 若$file没设定或为空值,则使用my.file.txt作传回值,同时将$file赋值为my.file.txt。(非空值时不作处理)
${file?my.file.txt}    # 若$file没设定,则将my.file.txt输出至STDERR。(空值及非空值时不作处理)
${file:?my.file.txt}   # 若$file没设定或为空值,则将my.file.txt输出至STDERR。(非空值时不作处理)

Tips:

以上的理解在于,你一定要分清楚unset与null及non-null这三种赋值状态。一般而言,: 与null有关,若不带 : 的话,null不受影响,若带 : 则连null也受影响。

还有哦,${#var}可计算出变量值的长度:

${#file} # 可得到27,因为/dir1/dir2/dir3/my.file.txt刚好是27个字节…

接下来,再为大家介稍一下bash的数组(array)处理方法。

一般而言,A="a b c def"这样的变量只是将$A替换为一个单一的字串,但是改为A=(a b c def),则是将$A定义为数组… bash的数组替换方法可参考如下方法:

${A[@]}或${A[*]} # 可得到a b c def(全部组数)
${A[0]} # 可得到a(第一个组数),${A[1]}则为第二个组数…
${#A[@]}或${#A[*]} #可得到4(全部组数数量)
${#A[0]} #可得到1(即第一个组数(a)的长度),${#A[3]}可得到3(第四个组数(def)的长度)
A[3]=xyz # 则是将第四个组数重新定义为xyz…

能够善用bash的$()${}可大大提高及简化shell在变量上的处理能力哦~~~ ^_^

好了,最后为大家介绍$(())的用途吧:它是用来作整数运算的。在bash中,$(())的整数运算符号大致有这些:+ - * /:分别为“加、减、乘、除”。%:余数运算。& | ^!:分别为“AND、OR、XOR、NOT”运算。 例:

$ a=5;b=7;c=2
$ echo $((a+b*c))
19
$ echo $(((a+b)/c))
6
$ echo $(((a*b)%c))
1

$(())中的变量名称,可于其前面加$(($a + $b * $c))也可得到19的结果

此外,$(())还可作不同进位(如二进位、八进位、十六进位)作运算呢,只是,输出结果皆为十进制而已:

echo $((16#2a)) # 结果为42(16进位转十进制)

以一个实用的例子来看看吧:假如当前的umask是022,那么新建文件的权限即为: umask022 echo “obase=8;You can't use 'macro parameter character #' in math mode(umask))))” | bc 644

事实上,单纯用(())也可重定义变量值,或作testing:

a=5;((a++)) #可将$a重定义为6 a=5;((a—)) 则为a=4 a=5;b=7;((a < b)) 会得到0(true)的返回值。

常见的用于(())的测试符号有如下这些: <:小于 >:大于 <=:小于或等于 >=:大于或等于 ==:等于 !=:不等于

不过,使用(())作整数测试时,请不要跟[ ]的整数测试搞混乱了。(更多的测试我将于第十章为大家介绍)怎样?好玩吧..^_^ okay,这次暂时说这么多…

上面的介绍,并没有详列每一种可用的状态,更多的,就请读者参考手册文件啰…

第9问:@与* 差在哪?

要说$@$*之前,需得先从shell script的positional parameter谈起…

我们都已经知道变量(variable)是如何定义及替换的,这个不用再多讲了。但是,我们还需要知道有些变量是shell内定的,且其名称是我们不能随意修改的,其中就有positional parameter在内。在shell script中,我们可用0,1,2,3…这样的变量分别提取命令行中的如下部份:

script_name parameter1 parameter2 parameter3…

我们很容易就能猜出$0就是代表shell script名称(路径)本身,而$1就是其后的第一个参数,如此类推… 须得留意的是IFS的作用,也就是,若IFS被quoting处理后,那么positional parameter也会改变。如下例:

my.sh p1 "p2 p3" p4

由于在p2与p3之间的空白键被soft quote所关闭了,因此my.sh中的$2是”p2 p3”而$3则是p4…

还记得前两章我们提到fucntion时,我不是说过它是script中的script吗?^_^ 是的,function一样可以读取自己的(有别于script的)postitional parameter,惟一例外的是$0而已。举例而言:假设my.sh里有一个function叫my_fun,若在script中跑my_fun fp1 fp2 fp3,那么function内的$0是my.sh,而$1则是fp1而非p1了…不如写个简单的my.sh script看看吧:

#!/bin/bash
my_fun() {
echo '$0 inside function is '$0
echo '$1 inside function is '$1
echo '$2 inside function is '$2
}
echo '$0 outside function is '$0
echo '$1 outside function is '$1
echo '$2 outside function is '$2
my_fun fp1 "fp2 fp3"

然后在command line中跑一下script就知道了:

chmod +x my.sh
./my.sh p1 "p2 p3"
$0 outside function is ./my.sh
$1 outside function is p1
$2 outside function is p2 p3
$0 inside function is ./my.sh
$1 inside function is fp1
$2 inside function is fp2 fp3

然而,在使用positional parameter的时候,我们要注意一些陷井哦:

10不是替换第10个参数,而是替换第一个参数(1)然后再补一个0于其后!

也就是,my.sh one two three four five six seven eigth nine ten这样的command line,my.sh里的$10不是ten而是one0哦…小心小心!要抓到ten的话,有两种方法:

方法一是使用我们上一章介绍的${ },也就是用${10}即可。

方法二,就是shift了。用通俗的说法来说,所谓的shift就是取消positional parameter中最左边的参数$0不受影响)。其预设值为1,也就是shiftshift 1都是取消$1 ,而原本的$2则变成$1$3变成$2 ……若shift 3则是取消前面三个参数,也就是原本的$4将变成$1

那,亲爱的读者,你说要shift掉多少个参数,才可用1取得{10}呢?^_^

okay,当我们对positional parameter有了基本概念之后,那再让我们看看其他相关变量吧。

首先是$#:它可抓出positional parameter的数量。以前面的my.sh p1 "p2 p3"为例:由于p2与p3之间的IFS是在soft quote中,因此$#可得到2的值。但如果p2与p3没有置于quoting中话,那$#就可得到3的值了。同样的道理在function中也是一样的…

因此,我们常在shell script里用如下方法测试script是否有读进参数:

[ $# = 0 ]

假如为0,那就表示script没有参数,否则就是有带参数…

接下来就是$@$*:精确来讲,两者只有在soft quote中才有差异,否则,都表示“全部参数”($0除外)。举例来说好了:若在command line上跑my.sh p1 "p2 p3" p4的话,不管是$@还是$*,都可得到p1 p2 p3 p4就是了。但是,如果置于soft quote中的话:

  • "$@" 则可得到”p1” “p2 p3” “p4”这三个不同的词段(word)
  • "$*"则可得到”p1 p2 p3 p4”这一整串单一的词段。

我们可修改一下前面的my.sh,使之内容如下:

#!/bin/bash
my_fun() {
echo "$#"
}
echo 'the number of parameter in "$@" is '$(my_fun "$@")
echo 'the number of parameter in "$*" is '$(my_fun "$*")

然后再执行./my.sh p1 “p2 p3” p4就知道@与*差在哪了…^_^

the number of parameter in "$@" is 3
the number of parameter in "$*" is 1

第10问:&& 与 || 差在哪?

好不容易,进入两位数的章节了…一路走来,很辛苦吧?也很快乐吧?^_^ 在解答本章题目之前,先让我们了解一个概念:return value!

我们在shell下跑的每一个command或function,在结束的时候都会传回父行程一个值,称为return value。在shell command line中可用$?这个变量得到最"新"的一个return value,也就是刚结束的那个行程传回的值。Return Value(RV)的取值为0-255之间,由程序(或script)的作者自行定议:

  • 若在script里,用exit RV来指定其值,若没指定,在结束时以最后一道命令之RV为值。
  • 若在function里,则用return RV来代替exit RV即可。

Return Value的作用,是用来判断行程的退出状态(exit status),只有两种:

  • 0的话为“真”(true)
  • 非0的话为“假”(false)

举个例子来说明好了:假设当前目录内有一份my.file的文件,而no.file是不存在的:

$ touch my.file
$ ls my.file
$ echo $? # first echo
0
$ ls no.file
ls: no.file: No such file or directory
$ echo $? # second echo
1
$ echo $? # third echo
0

上例的第一个echo是关于ls my.file的RV,可得到0的值,因此为true;第二个echo是关于ls no.file的RV,则得到非0的值,因此为false;第三个echo是关于第二个echo $?的RV,为0的值,因此也为true。

请记住:每一个command在结束时都会送回return value的! 不管你跑甚么样的命令…然而,有一个命令却是”专门”用来测试某一条件而送出return value以供true或false的判断,它就是test命令了!

若你用的是bash,请在command line下打man testman bash来了解这个test的用法。这是你可用作参考的最精确的文件了,要是听别人说的,仅作参考就好…下面我只简单作一些辅助说明,其余的一律以man为准:

首先,test的表示式我们称为expression,其命令格式有两种:

test expression 
or:
[ expression ]

(请务必注意[ ]之间的空白键﹗)

用哪一种格式没所谓,都是一样的效果。(我个人比较喜欢后者…)其次,bash的test目前支持的测试对像只有三种:

  • string:字串,也就是纯文字。
  • integer:整数(0或正整数,不含负数或小数点)。
  • file:文件。

请初学者一定要搞清楚这三者的差异,因为test所用的expression是不一样的。

[ "$A" = 123 ]  # 是字串的测试,以测试$A是否为1、2、3这三个连续的“文字”。
[ "A" -eq 123 ] # 是整数的测试,以测试$A是否等于“一百二十三”。
[ -e "$A" ]     # 是关于文件的测试,以测试123这份“文件”是否存在。

第三,当expression测试为“真”时,test就送回0(true)的return value,否则送出非0(false)。若在expression之前加上一个!(感叹号),则是当expression为“假时”才送出0,否则送出非0。

同时,test也允许多重的复合测试:

expression1 -a expression2 # 当两个exrepssion都为true,才送出0,否则送出非0。
expression1 -o expression2 # 只需其中一个exrepssion为true,就送出0,只有两者都为false才送出非0。

例如:

[ -d "$file" -a -x "$file" ]

是表示当$file是一个目录、且同时具有x权限时,test才会为true。

第四,在command line中使用test时,请别忘记命令行的“重组”特性,也就是在碰到meta时会先处理meta再重新组建命令行。(这个特性我在第二及第四章都曾反复强调过)比方说,若test碰到变量或命令替换时,若不能满足expression格式时,将会得到语法错误的结果。举例来说好了:关于[ string1 = string2 ]这个test格式,在=号两边必须要有字串,其中包括空(null)字串(可用soft quote或hard quote取得)。

假如$A目前没有定义,或被定议为空字串的话,那如下的写法将会失败:

$ unset A
$ [ $A = abc ]
[:=:unary operator expected

这是因为命令行碰到这个meta时,会替换A的值,然后再重组命令行,那就变成了:[ = abc ]

如此一来=号左边就没有字串存在了,因此造成test的语法错误﹗但是,下面这个写法则是成立的:

$ [ "$A" = abc ]
$ echo $?
1

这是因为在命令行重组后的结果为:[ "" = abc ]。由于=左边我们用soft quote得到一个空字串,而让test语法得以通过…读者诸君请务必留意这些细节哦,因为稍一不慎,将会导至test的结果变了个样!

若您对test还不是很有经验的话,那在使用test时不妨先采用如下这一个”法则”:

假如在test中碰到变量替换,用soft quote是最保险的﹗若你对quoting不熟的话,请重新温习第四章的内容吧…^_^

okay,关于更多的test用法,老话一句:请看man page吧!^_^ 虽然洋洋洒洒讲了一大堆,或许你还在嘀咕….那…那个return value有啥用啊?!问得好﹗

告诉你:return value的作用可大了﹗若你想让你的shell变“聪明”的话,就全靠它了:

有了return value,我们可以让shell跟据不同的状态做不同的时情…

这时候,才让我来揭晓本章的答案吧~~~ ^_^ && 与 || 都是用来“组建”多个command line用的:

  • command1 && command2:其意思是command2只有在RV为0(true)的条件下执行。
  • command1 || command2:其意思是command2只有在RV为非0(false)的条件下执行。

来,以例子来说好了:

$ A=123
$ [ -n "$A" ] && echo "yes!it's ture."
yes!it's ture.
$ unset A
$ [ -n "$A" ] && echo "yes!it's ture."
$ [ -n "$A" ] || echo "no,it's NOT ture."
no,it's NOT ture.

(注:[ -n string ]是测试string长度大于0则为true。)

上例的第一个&&命令行之所以会执行其右边的echo命令,是因为上一个test送回了0的RV值﹔但第二次就不会执行,因为test送回非0的结果…同理,||右边的echo会被执行,却正是因为左边的test送回非0所引起的。

事实上,我们在同一命令行中,可用多个&&或||来组建呢:

$ A=123
$ [ -n "$A" ] && echo "yes!it's ture." || echo "no,it's NOT ture."
yes!it's ture.
$ unset A
$ [ -n "$A" ] && echo "yes!it's ture." || echo "no,it's NOT ture."
no,it's NOT ture.

怎样,从这一刻开始,你是否觉得我们的shell是“很聪明”的呢?^_^ 好了,最后,布置一道习题给大家做做看…

下面的判断是:当$A被赋与值时,再看是否小于100,否则送出too big!:

$ A=123
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
too big!

若我将A取消,照理说,应该不会送文字才对啊(因为第一个条件就不成立了)…

$ unset A
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
too big!

为何上面的结果也可得到呢?又,如何解决之呢?(提示:修改方法很多,其中一种方法可利用第七章介绍过的command group…)快﹗告我我答案﹗其余免谈…

原文链接:https://segmentfault.com/a/1190000004324819 原文链接:http://bbs.chinaunix.net/thread-218853-1-1.html

注:由于微信文章字数限制,本文章设置为连载系列,敬请继续关注~

原文发布于微信公众号 - 马哥Linux运维(magedu-Linux)

原文发表时间:2016-04-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏CSDN技术头条

提高代码可读性的10个技巧

以下为译文: 如果你的代码很容易阅读,这也会帮助你调试自己的程序,让工作变得更容易。 代码可读性是计算机编程领域的一个普遍课题,这也是作为开发人员首先要学习的...

2047
来自专栏狮乐园

简单探索 js 中 something >> 0 的原理

关于这个问题是今天改公司项目小程序的一个bug时看到的,修复这个bug的解决方法是需要引入 String.prototype.padStart 的 polyfi...

1173
来自专栏静晴轩

IOS 8 Safari JIT bug影响jQuery和underscore

前端时间为移动游戏做一个网页活动需求(9宫格的刮奖),遇到一个很诡异的问题:Android端OK,就是在Ios设备上,点击非第一块区域,显示却是第一块区域被刮开...

3596
来自专栏Pythonista

Python之路,Day1 - Python基础1

python的创始人为吉多·范罗苏姆(Guido van Rossum)。1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解...

1995
来自专栏C/C++基础

C++为什么要引入异常处理机制

在程序设计中,错误时不可避免的。及时有效的发现错误,并作出适当的处理,无论是在软件的开发阶段还是在维护阶段都是至关重要的。错误修复技术是提高代码健壮性的最有效的...

911
来自专栏玄魂工作室

Hacker基础之Python篇:一、环境安装和基础知识

0x01. 前言 emmmmmmm...你只需知道这是一门用途很广的语言,上到大数据AI,下到Linux运维,都可以使用Python,当然,黑客也用Pyth...

3016
来自专栏恰童鞋骚年

《你必须知道的.NET》读书笔记:从Hello World认识IL

  IL是.NET框架中间语言(Intermediate Language)的缩写。使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但...

1122
来自专栏禁心尽力

多线程之策略模式

今天给各位分享一种Java23种设计模式中最常见的设计模式--策略模式。为什么将策略模式和多线程绑在一起呢,不知道各位有没有注意过我们在进行多线程编程的时候,创...

2137
来自专栏Python入门

你还在为Python中文乱码而感到烦恼?今天老司机给你讲讲!

有没有遇到过这样的问题,读取文件被提示“UnicodeDecodeError”、爬取网页得到一堆乱码,其实这些都是编码惹的祸,如果不能真正理解编码的问题所在,就...

1863
来自专栏java一日一条

编写难于测试的代码的5种方式

有一次,我在一个讲座上听到主持人问听众如何故意编写难于测试的代码。在场的小伙伴都惊呆了,因为没有任何人会故意写这种糟糕的代码。我记得他们甚至给不出一个好的答案。

1162

扫码关注云+社区

领取腾讯云代金券