命令行扩展
之前,我们写一些shell命令,按回车,它就执行了。不知道为什么?但是很神奇。
这一节我们来探讨下这里面的门门道道。
1 | [root@vm-101 ~]# test1(){ echo "这是一个函数。"; echo $[ 1 + 2 ]; }; test1 |
bash解释器一次扫描标记,按照以下顺序来处理,扩展,最后执行。
花括号扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15[root@vm-101 ~]# echo {{1,2,3},{a..c}}
1 2 3 a b c
[root@vm-101 ~]# echo file_{1,2,3}.log
file_1.log file_2.log file_3.log
[root@vm-101 ~]# echo file_{A..D}.txt
file_A.txt file_B.txt file_C.txt file_D.txt
[root@vm-101 ~]# touch a.txt
[root@vm-101 ~]# ls a.txt
a.txt
[root@vm-101 ~]# mv a.txt{,.bak} # 这种方式在一些情况下很方便。
[root@vm-101 ~]# ls a.txt*
a.txt.bak代字符扩展
参数扩展和变量扩展
参数包括:命令行参数,位置参数,特殊参数
变量包括:用户定义的变量和关键字变量
如果这些参数和变量被单引号包起来的时候,就会被转义,不会扩展。
变量在使用的时候,如果$符号前面有\,也会被转义,不会扩展。
如果用双引号包起来的时候,不会被转义,会扩展。
详解在21章
算数扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14$(( expression ))
$[ expression ] # 和上面一样
内置命令let
[root@vm-101 tmp]# echo $(( 2*3 + 3*3 ))
15
[root@vm-101 tmp]# x=3
[root@vm-101 tmp]# echo $(( 2*3 + 3*x )) # $(( ))里面使用变量的时候,变量的$可以省略。但是我觉得不是一个好习惯。
15
[root@vm-101 tmp]# let a=x+3 b=$x*4 # 这里还是带上$符号,虽然不带也可以
[root@vm-101 tmp]# echo $a $b
6 12命令替换
1
2
3
4
5
6
7
8$(COMMAND)
`COMMAND`
# 两种写法一样。眼神要的用第二种,眼神不好的用第一种。
[root@vm-101 tmp]# echo $(pwd)
/tmp
[root@vm-101 tmp]# echo `pwd`
/tmp分词
这个和一个关键字变量有关系。那就是IFS变量。bash会用它去对输入的命令语句进行分词。
默认情况下,bash会使用它的默认值(空格,制表符,换行符)1
2[root@vm-101 ~]# echo -e "--$IFS--" | sed 'N;s/ / blank /; s/\t/ table /; s/\n/ new /'
-- blank table new --暂时不用管这个sed是干嘛的。简单说就是:
把IFS里面的空格符替换成了’ blank ‘
把IFS里面的制表符替换成了’ table ‘
把IFS里面的换行符替换成了’ new ‘
目的是方便大家看到IFS里面的内容。
路径扩展
路径扩展就是文件名补全。这里涉及到一些特殊符号:* ? [ ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23[root@vm-101 ~]# ls /etc/host*
/etc/host.conf /etc/hostname /etc/hosts /etc/hosts.allow /etc/hosts.deny
[root@vm-101 ~]# ls /etc/host?
/etc/hosts
[root@vm-101 ~]# ls /etc/hosts.[a,d]*
/etc/hosts.allow /etc/hosts.deny
[root@vm-101 ~]# ls "/etc/hosts.[a,d]*" # 单引号,双引号中,都会失效
ls: 无法访问/etc/hosts.[a,d]*: 没有那个文件或目录
[root@vm-101 ~]# ls '/etc/hosts.[a,d]*'
ls: 无法访问/etc/hosts.[a,d]*: 没有那个文件或目录
[root@vm-101 ~]# files=/etc/host*
[root@vm-101 ~]# set | grep files # 看看环境变量里面的值情况
files='/etc/host*'
[root@vm-101 ~]# echo '$files' # 单引号,被转义了
$files
[root@vm-101 ~]# echo "$files" # 有双引号,路径扩展失效,只显示出变量的值
/etc/host*
[root@vm-101 ~]# echo $files # 得到$files的值后,再进行路径扩展,最终得到文件路径列表
/etc/host.conf /etc/hostname /etc/hosts /etc/hosts.allow /etc/hosts.deny进程替换
进程替换的语法前面已经提到了
<( COMMAND )
和 >( COMMAND )1
2
3
4
5
6[root@vm-101 ~]# while read field1 field2 field3 others
> do
> echo "# $field1 # $field2 # $field3 # $others #"
> done < <( cat /etc/hosts )
# 127.0.0.1 # localhost # localhost.localdomain # localhost4 localhost4.localdomain4 #
# ::1 # localhost # localhost.localdomain # localhost6 localhost6.localdomain6 #题外话:
有人要说我也可以用cat /etc/hosts | while xxxxx。
是的,没错。这样也可以,但是有一个问题。
还记得前面说的吗?管道是会产生一个新的子进程?导致后面的while是在子进程运行的。里面的变量,在外面是拿不到的。
只能不停的echo echo echo。主进程不能拿来用,干瞪眼。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26[root@vm-101 ~]# cat test.sh
# 注释是个好习惯
cat /etc/hosts | while read f1 f2 f3 f4 others # 这里和IFS有关系。每行都用IFS分词
do
while_var=111
done
echo "$while_var"
echo "--------------------------------------------"
while read f1 f2 f3 f4 others
do
while_var=111
done < <( cat /etc/hosts )
echo "$while_var"
exit 0
[root@vm-101 ~]# bash test.sh
全局变量 # # 没有拿到$while_var的值
--------------------------------------------
全局变量 # 111 # 拿到了$while_var的值
再次小小总结
到这里,恭喜你,一般的问题基本难不倒你了。可以写出一些比较厉害的脚本。一般的坑也奈何不了你了。