bash学习之八:shell expansion(shell扩展)

shell扩展是个大话题,主要有以下几种扩展:

1、brace expansion(花括号扩展);

2、tilde expansion(代字符号扩展);

3、parameter expansion(参量扩展);

4、variable expansion(变量扩展);

5、command substitution(命令替换);

6、arithmetic expansion(算术扩展);

7、process substitution(过程替换);

8、word splitting(词分离);

9、file name expansion(路径名扩展)。

以上九种扩展是按顺序进行的,首先进行brace expansion,最后进行file expansion。下面按顺序分别介绍各种扩展的用法。

一、brace expansion

关于brace expansion,首先要和参量扩展、变量扩展区分,看下面的输出:

wangjk@wangjiankun:~$ cat expansion.sh                                         
     1  #!/bin/bash
     2
     3  echo "brace expansion"
     4  echo sp{el,il,al}l
     5
     6  echo
     7  echo "variable expansion"
     8  firstname=wang
     9  echo ${firstname}jiankun
wangjk@wangjiankun:~$ ./expansion.sh
brace expansion
spell spill spall

variable expansion
wangjiankun
wangjk@wangjiankun:~$

关于brace expansion要注意花括号中不允许出现不加引号的空格,如有不加引号的空格将不进行扩展,如下所示:

wangjk@wangjiankun:~$ cat expansion1.sh                                        
     1  #!/bin/bash
     2
     3  echo sp{el,il, al}l
     4  echo sp{el,il," al"}l
     5  echo sp{el,il," "al}l
wangjk@wangjiankun:~$ ./expansion1.sh                                         
sp{el,il, al}l
spell spill sp all
spell spill sp all
wangjk@wangjiankun:~$

二、tilde expansion

首先解释一个单词,

tilde

1 : a mark ~ placed especially over the letter n to denote the sound /ny/ or over vowels to indicate nasality
2 : the mark tilde used to indicate negation in logic and the geometric relation “is similar to” in mathematics

总之,就是符号“~”。

关于~,我们很常用,比如将目录切换到某用户(例如:root)的家目录就可以这样做:cd ~root,如下所示:

wangjk@wangjiankun:~$ pwd
/home/wangjk
wangjk@wangjiankun:~$ cd ~root
wangjk@wangjiankun:/root$ pwd
/root
wangjk@wangjiankun:/root$ cd ~wangjk
wangjk@wangjiankun:~$ pwd
/home/wangjk
wangjk@wangjiankun:~$

关于tilde expansion,我们只知道当它后跟用户名时,代表是该用户的家目录,当它后跟以“/”开头的路径时,代表是当前用户的家目录就可以了,也就是说,它的默认值是当前用户的家目录,如下所示:

wangjk@wangjiankun:~$ pwd
/home/wangjk
wangjk@wangjiankun:~$ whoami
wangjk
wangjk@wangjiankun:~$ cd ~root
wangjk@wangjiankun:/root$ pwd
/root
wangjk@wangjiankun:/root$ cd ~/scripts
wangjk@wangjiankun:~/scripts$ pwd
/home/wangjk/scripts
wangjk@wangjiankun:~/scripts$ cd ~root/123
wangjk@wangjiankun:/root/123$ pwd
/root/123
wangjk@wangjiankun:/root/123$

注:它还有“~+”、“~-”的用法,这里不再介绍,其实很简单,试一下便知。

三、shell parameter and variable expansion

说到参量和变量扩展,我们必须介绍两个符号“$”和“{}”,“$”不但用于参量和变量扩展中,而且用于命令替换、算术扩展,它是参量、变量、命令、算术扩展的“引导符”。

在参数扩展(位置参数小于10)和变量扩展中,“{}”不是必要的,但是它可以起到保护变量的作用,其保护作用表现在变量名后是字符的情况,如下所示:

wangjk@wangjiankun:~$ cat expansion.sh
     1  #!/bin/bash
     2
     3  firstname=wang
     4  echo ${firstname}jiankun
     5  echo $firstnamejiankun
wangjk@wangjiankun:~$ ./expansion.sh
wangjiankun

wangjk@wangjiankun:~$

第五行的输出没有达到我们的目的,因为bash认为变量名为:firstnamejiankun,bash并不会自动识别变量名。在这种情况下,必须用“{}”。

在这里我再介绍一个间接扩展(indirct expansion)的例子,间接扩展要用到符号:“!”,输出如下所示:

wangjk@wangjiankun:~$ cat expansion.sh
     1  #!/bin/bash
     2
     3  wang=Wang
     4  firstname=wang
     5  echo ${!firstname}jiankun
wangjk@wangjiankun:~$ ./expansion.sh
Wangjiankun
wangjk@wangjiankun:~$

这里,由于“!”的存在,bash将变量firstname的值wang又作为变量名,最终扩展的结果是:Wang,得到Wangjiankun。

关于参数、变量扩展,我再介绍最后一个话题:就是如下形式的扩展:${VAR:=variable},先看下面的输出:

wangjk@wangjiankun:~$ cat expansion.sh
     1  #!/bin/bash
     2
     3  firstname=wang
     4  echo ${firstname:=WANG}jiankun
     5  echo ${name:=WANG}jiankun
wangjk@wangjiankun:~$ ./expansion.sh
wangjiankun
WANGjiankun
wangjk@wangjiankun:~$

如果变量已定义,不进行赋值操作,直接按原定义扩展;如果变量没有定义先赋值再扩展。

四、command substitution

命令替换,注意,是替换(substitution)!而不是执行!看下面的输出:

wangjk@wangjiankun:~$ cat command.sh
     1  #!/bin/bash
     2
     3  echo
     4  date
     5
     6  echo
     7 $(date)
wangjk@wangjiankun:~$ bash -x command.sh                                      
+ echo

+ date
Wed Jul 15 16:42:25 EDT 2009
+ echo

++ date
+ Wed Jul 15 16:42:25 EDT 2009
command.sh: line 7: Wed: command not found
wangjk@wangjiankun:~$ ./command.sh

Wed Jul 15 16:42:31 EDT 2009

./command.sh: line 7: Wed: command not found
wangjk@wangjiankun:~$

上面脚本的第七行虽然报错了,但不是命令替换出错了,相反,命令替换是成功的,但是bash将替换后的星期三:Wed看做是命令,所以导致了出错。这就是替换:bash将$()中的单词看做是命令,先将其执行,将执行后的输出原封不动的放在原地,任由bash处理。上面的Wed由于它在一行的行首,所以bash认为它是命令。

命令替换有且只有两种格式:

$(command)

`command`

这里要区分一下brace和parentheses,及花括号和小括号。前者用于变量和参量扩展,而后者用于命令替换,bash将$(word)中的word当做命令来执行。

五、arithmetic expansion

算术扩展的语法格式:$((EXPRESSION))

在算术扩展中有一个重要的点,就是在双小括号中可以进行参数扩展、命令替换、以及引号的移除。英文原文:All tokens in the expression undergo parameter expansion, command substitution, and quote removal. 看下面的输出:

wangjk@wangjiankun:~$ cat command.sh
     1  #!/bin/bash
     2
     3  echo
     4  val=3
     5
     6  echo
     7  echo $(($1+val+2+`date +%m`))
wangjk@wangjiankun:~$ bash -x command.sh
+ echo

+ val=3
+ echo

++ date +%m
+ echo 12
12
wangjk@wangjiankun:~$ bash -x command.sh 2
+ echo

+ val=3
+ echo

++ date +%m
+ echo 14
14
wangjk@wangjiankun:~$

第一次没有指定位置参数,所以输出为12(变量val=3,date命令输出的月份为7,再加上常量2);第二次指定位置参数$1为2,所以输出为14。

bash算术扩展的运算符和优先级与C语言中极其相似,这里不再介绍,必要时可以查看手册页。

bash的变量可以作为操作数出现在算术扩展的表达式中。

对于常量,以0(zero)开头表示是八进制数,以0x(0X)开头表示是十六进制数。bash支持2到64之间的任意进制数,关于它们如何表示,不详细介绍,只给出如下输出:

wangjk@wangjiankun:~$ cat command.sh
     1  #!/bin/bash
     2
     3  echo "octal numbers"
     4  echo "011==$((011))"
     5
     6  echo
     7  echo "hexadecimal numbers"
     8  echo "0x1a==$((0x1a))"
     9
    10  echo
    11  echo "arithmetic base 36"
    12  echo "36#z==$((36#z))"
    13  echo "36#Z==$((36#Z))"
    14
    15  echo
    16  echo "arithmetic base 37"
    17  echo "37#a==$((37#a))"
    18  echo "37#A==$((37#A))"
    19
    20  echo
    21  echo "arithmetic base 64"
    22  echo "64#@==$((64#@))"
    23  echo "64#_==$((64#_))"

wangjk@wangjiankun:~$ ./command.sh                                                                                
octal numbers
011==9

hexadecimal numbers
0x1a==26

arithmetic base 36
36#z==35
36#Z==35

arithmetic base 37
37#a==10
37#A==36

arithmetic base 64
64#@==62
64#_==63
wangjk@wangjiankun:~$

六、process substitution

过程替换是很难说清楚的一种替换,它与重定向也很难区分!如果不深究原理,表面的现象是:重定向是一口入,一口出;而process substitution可以多口入(事实上,它根本就没有“入”,不要被它的两个带尖的符号给迷惑了!)。

下面,我要通过例子把process substitution说清楚。先看下面的输出:

wangjk@wangjiankun:~$ ls -l j.txt
-rw-r--r-- 1 wangjk wangjk 0 Jul  3 19:25 j.txt
wangjk@wangjiankun:~$ ls -l <(true)
lr-x------ 1 wangjk wangjk 64 Jul 20 18:55 /dev/fd/63 -> pipe:[6193]
wangjk@wangjiankun:~$

以上两个命令的输出形式是一样的,都是列出了一个文件的信息,我们可不可以先这样类比:第二个命令中<(true)就是/dev/fd/63?

运行命令:wangjk@wangjiankun:~$ vim <(ls -l)得到下图所示的输出:

从上图中vim的最底下一行的显示可以看出这个文件的名字是:/dev/fd/63,看来<(true)还真是/dev/fd/63,这样我们就可以对process substitution中的substitution(替换)有一个全新的认识:<(LIST)或>(LIST)就是一个文件名。下面的输出可以说明这一点:

wangjk@wangjiankun:~$ ls -l <(true) >(true)                                    
l-wx------ 1 wangjk wangjk 64 Jul 20 19:30 /dev/fd/62 -> pipe:[6466]
lr-x------ 1 wangjk wangjk 64 Jul 20 19:30 /dev/fd/63 -> pipe:[6464]
wangjk@wangjiankun:~$

到此为止,我们清楚了所谓的process substitution的<(LIST)和>(LIST)就是一个用文件描述符表示的文件,那么<(LIST)和>(LIST)到底有什么区别呢?一时间,我没有想到合适的例子来解释二者的区别,现将某资料的英文原文摘录如下:

If the ">(LIST)" form is used, writing to the file will provide input for LIST. If the "<(LIST)" form is used, the file passed as an
argument should be read to obtain the output of LIST.

七、word splitting

如果参数扩展、命令替换、算术扩展没有在双引号中,shell将在其结果中进行词分离操作。

shell把$IFS中的每一个字符看做是分隔符,并且将其他扩展的结果在出现这些字符处进行分离。如果$IFS没有设置,默认情况下,它的值是"'<space><tab><newline>'",此时任何IFS字符序列都可以分离单词。关于词分离先只介绍这些,在今后的脚本和命令中遇到时,在逐个分析。

八、file name expansion

你可能感兴趣的:(Date,shell,command,bash,扩展,Numbers)