前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >shell十三问,为linux学习打基础(二)

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

作者头像
小小科
发布2018-05-03 16:37:44
8640
发布2018-05-03 16:37:44
举报
文章被收录于专栏:北京马哥教育北京马哥教育

本文整理并转自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的:

代码语言:javascript
复制
./my.script

现在改成这样即可:

代码语言:javascript
复制
source ./my.script

或:

代码语言:javascript
复制
../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

代码语言:javascript
复制
#!/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

代码语言:javascript
复制
#!/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"

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

代码语言:javascript
复制
$ ./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的定义方式有两种: 方式一:

代码语言:javascript
复制
function function_name {
command1
command2
command3
…
}

方式二:

代码语言:javascript
复制
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)的。所谓的命令替换与我们第五章学过的变量替换差不多,都是用来重组命令行:

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

例如:

代码语言:javascript
复制
$ echo the last sunday is $(date -d "last sunday" +%Y-%m-%d)

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

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

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

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

代码语言:javascript
复制
command1 `command2 \`command3\` `

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

代码语言:javascript
复制
command1 $(command2 $(command3))

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

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

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

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

代码语言:javascript
复制
$ A=B
$ echo $AB

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

代码语言:javascript
复制
$ echo ${A}B
BB

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

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

代码语言:javascript
复制
file=/dir1/dir2/dir3/my.file.txt

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

代码语言:javascript
复制
${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

记忆的方法为:

代码语言:javascript
复制
# 是去掉左边(在键盘上#在$之左边)
% 是去掉右边(在键盘上%在$之右边)
单一符号是最小匹配,两个符号是最大匹配。

代码语言:javascript
复制
${file:0:5} # 提取最左边的5个字节:/dir1
${file:5:5} # 提取第5个字节右边的连续5个字节:/dir2

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

代码语言:javascript
复制
${file/dir/path} # 将第一个dir提换为path:/path1/dir2/dir3/my.file.txt
${file//dir/path} # 将全部dir提换为path:/path1/path2/path3/my.file.txt

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

代码语言:javascript
复制
${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}可计算出变量值的长度:

代码语言:javascript
复制
${#file} # 可得到27,因为/dir1/dir2/dir3/my.file.txt刚好是27个字节…

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

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

代码语言:javascript
复制
${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”运算。 例:

代码语言:javascript
复制
$ 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的结果

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

代码语言:javascript
复制
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…这样的变量分别提取命令行中的如下部份:

代码语言:javascript
复制
script_name parameter1 parameter2 parameter3…

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

代码语言:javascript
复制
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看看吧:

代码语言:javascript
复制
#!/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就知道了:

代码语言:javascript
复制
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是否有读进参数:

代码语言:javascript
复制
[ $# = 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,使之内容如下:

代码语言:javascript
复制
#!/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就知道@与*差在哪了…^_^

代码语言:javascript
复制
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是不存在的:

代码语言:javascript
复制
$ 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,其命令格式有两种:

代码语言:javascript
复制
test expression 
or:
[ expression ]

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

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

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

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

代码语言:javascript
复制
[ "$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也允许多重的复合测试:

代码语言:javascript
复制
expression1 -a expression2 # 当两个exrepssion都为true,才送出0,否则送出非0。
expression1 -o expression2 # 只需其中一个exrepssion为true,就送出0,只有两者都为false才送出非0。

例如:

代码语言:javascript
复制
[ -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目前没有定义,或被定议为空字串的话,那如下的写法将会失败:

代码语言:javascript
复制
$ unset A
$ [ $A = abc ]
[:=:unary operator expected

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

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

代码语言:javascript
复制
$ [ "$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)的条件下执行。

来,以例子来说好了:

代码语言:javascript
复制
$ 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所引起的。

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

代码语言:javascript
复制
$ 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!:

代码语言:javascript
复制
$ A=123
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
too big!

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

代码语言:javascript
复制
$ 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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-04-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 马哥Linux运维 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第6问:exec跟source差在哪?
  • 第7问:( ) 与 { } 差在哪?
  • 第8问:(())与( ) 還有${ } 差在哪?
  • 第9问:@与* 差在哪?
  • 第10问:&& 与 || 差在哪?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档