14. $(( )) $( ) 还有${ } 差在哪?

bash shell 中,$( ) ` ` (反引号) 都是用来做命令替换用(command substitution

的。  所谓的命令替换与我们第五章学过的变量替换差不多,都是用来重组命令行:

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

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

` ` 很容易与 ' ' ( 单引号)搞混乱

在多层次的复合替换中,` ` 须要额外的跳脱( \` )处理,而 $( ) 则比较直观

command1 `command2 \`command3\` `

` ` 基本上可用在全部的 unix shell 中使用,若写成 shell script ,其移植性比较高。

$( ) 并不见的每一种 shell 都能使用,我只能跟你说,若你用 bash2 的话,肯定没问题...

再让我们看 ${ } ... 它其实就是用来作变量替换用的啦

一般情况下,$var ${var} 并没有啥不一样。 

但是用 ${ } 会比较精确的界定变量名称的范围,比方说:  

$ A=B 

$ echo $AB 

为了完整起见,我这里再用一些例子加以说明 ${ } 的一些特异功能: 

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

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 (

空值时不作处理

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

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

 

 

15.数组array

接下来,再为大家介稍一下 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 中,$(( )) 的整数运算符号大致有这些: 

+ - * / :分别为 "加、减、乘、除" 

% :余数运算 

& | ^ !:分别为 "ANDORXORNOT" 运算。 

例:  

代码:

$ a=5; b=7; c=2 

$ echo $(( a+b*c )) 

19 

$ echo $(( (a+b)/c )) 

$ echo $(( (a*b)%c)) 

1

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

a=5; ((a++)) 可将 $a 重定义为

a=5; ((a--)) 则为 a=4 

a=5; b=7; ((a < b)) 会得到 0 (true) 的返回值。 

常见的用于 (( )) 的测试符号有如下这些:  

<:小于 

>:大于 

<=:小于或等于 

>=:大于或等于 

==:等于 

!=:不等于

 

16. $@  $* 差在哪?

shell script 中,我们可用 $0, $1, $2, $3 ... 这样的变量分别提取命令行中的如下部份:   

script_name parameter1 parameter2 parameter3 ...

function 一样可以读取自己的(有别于 script ) postitional parameter ,惟一例外

的是 $0 而已。

举例而言:假设 my.sh 里有一个 fucntion my_fun , 若在 script 中跑 my_fun fp1 fp2 fp3 ,那么,function 内的 $0 my.sh ,而 $1 则是 fp1 而非 p1 ... 

#!/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 就是取消位置参数中最左边的参数( $0

受影响) 

其默认值为 1 ,也就是 shift shift 1 都是取消 $1 ,而原本的 $2 则变成 $1$3

$2 ... 

shift 3 则是取消前面三个参数,也就是原本的 $4 将变成 $1 ... 

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

因此,我们常在 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" 这一整串单一的词段。 

#!/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 就知道 $@ $* 差在哪了 ... ^_^

 

16.   return value(RV)

Return Value(RV) 的取值为 0-255 之间,由程序( script)的作者自行定议: 

* 若在 script 里, exit RV 来指定其值,若没指定,在结束时以最后一道命令之 RV 为值。  

* 若在 function 里,则用 return RV 来代替 exit RV 即可。 

bash test 目前支持的测试对像只有三种:

A=123 这个变量为例

* [ "$A" = 123 ]:是字符串的测试,以测试 $A 是否为 123 这三个连续的"文字

* [ "$A" -eq 123 ]:是整数的测试,以测试 $A 是否等于"一百二十三" 

* [ -e "$A" ]:是关于文件的测试,以测试 123 这份"文件"是否存在。 

代码:

$ unset A 

$ [ $A = abc ] 

[: =: unary operator expected

 

16.     && || 都是用来"组建"多个 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 送回了

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.

 

下面的判断是:当 $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!