变量的作用域子shell之临时的局部变量函数中的局部变量 local通过子shell与小括号 ()通过管道符 |外部命令脚本shell脚本解析的第一行shell debug条件与比较条件运算符数值比较复合条件与或非多条命令合并在一起()运算符{}运算符深入各种括号之中深入掌握括号语法格式$(cmd)和`cmd`双小括号 (( ))单中括号 []双中括号[[ ]]大括号、花括号 {}四种模式匹配替换结构字符串提取和替换
$0 | 当前脚本的文件名 |
---|---|
$n | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。 |
$@ | 传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同,下面将会讲到。 |
$? | 上个命令的退出状态,或函数的返回值。 |
$$ | 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。 |
示例:
xxxxxxxxxx
1[root@666 ~]# ps | grep $$ # 如果开启终端高亮,会看到22888这个进程号是被grep高亮匹配到的。
222888 pts/0 00:00:00 bash
3[root@666 ~]#
xxxxxxxxxx
2411、局部变量
2
3# 顾名思义,局部变量就是在局部起作用的变量,用local内建命令定义。在函数定义中,没有明确定义为局部变量的变量是全局变量,如下
4
5[root@shell ~]# function defval(){
6 a=66
7 }
8[root@shell ~]# echo $a # 未调用函数,无变量,变量不存在
9
10[root@shell ~]# defval # 调用函数,载入变量定义,变量存在定义
11[root@shell ~]# echo $a
1266
13
14# 上面的变量a在函数defval调用后,被载入当前环境变量,与直接export a=66直接定义基本一致。
15
16# 下面添加local修饰符,效果是变量仅在函数内部,不出函数。如果想要调用,则只能在函数内部传递。
17[root@shell ~]# function defval(){ local a=77; export b=$a ; }
18[root@shell ~]# defval
19[root@shell ~]# echo $a # 此时还是输出的前面的环境变量,非local定义的变量。
2066
21[root@shell ~]# echo $b # 通过变量传递将local定义的$a变量值传递出来。
2277
23# 这里使用local定义局部变量a,但是在函数外却访问不到,输出的值仍然是前面我们赋的值
24
xxxxxxxxxx
231
21、在子shell中定义的变量也是局部变量
3
4[root@shell ~]# (a=3;echo $a)
53
6[root@shell ~]# echo $a
766
8
9# 这里最后输出的仍然是66,还是原来的值,即使没有使用local命令定义
10
112、子shell
12
13# 通常情况下我们在终端输入的命令会作为当前终端shell的子进程来运行,子shell的出现使得我们并行处理变成可能,看后面。
14# 创建子进程的方法如下
15
163、通过小括号 ()
17
18# 在小括号中运行的命令就是在子shell中运行的
19
20[root@vm3 ~]# (echo;pstree)
21├─sshd───sshd───bash───bash───pstree
22
23
可以重定向,此处说明还可以传递变量,且限制在一个子shell变量域中,不影响父变量域
xxxxxxxxxx
111
2# 在管道中定义的变量也是局部变量,管道符相连的命令也是在子shell中运行的
3
4[root@vm3 ~]# a=3|echo $a
566
6[root@vm3 ~]# echo $a
766
8
9# 可以看出两次输出的都是66,意味着每一个管道是一个子shell
10
11# 综上说明,在管道、子shell里只能读取全局变量的值,而不能修改,函数例外。
外部命令指的是非bash、sh等内置的命令,外部命令是在子shell中运行的,其中的变量往往只在局部使用。
xxxxxxxxxx
141[root@shell ~]# cat t.sh
2
3
4(cat /root/expr1|bc >> /root/rs) &
5(cat /root/expr2|bc >> /root/rs) &
6wait
7cat /root/rs
8[root@shell ~]# ./t.sh
915
1040
11
12# 这里的wait会等待所有前面放入后台的进程执行完毕才开始运行
13
14# Note:在脚本中内建命令比外部命令执行更快速,因为内建命令不需要fork出进程来执行。
x
101
2# lends you some flexibility on different systems
3# 在不同发行版linux中提供一定的灵活性,不好的地方是,有可能在一个多用户的系统中,别人在你的$PATH中放置了一个bash,可能出现错误。
4
5
6# gives you explicit control on a given system of what executable is called
7# 明确调用指定shell解析脚本。在某些情况下更安全,因为它限制了代码注入的可能
8
9
10# 它是#!/bin/bash。历史bash从NetBSD移植到Linux并更名为dash (Debian Almquist Shell),使用man sh命令和man bash命令去观察,可以发现sh本身就是dash,此时的兼容性可能有一定不足。
11
12# 其它
13比如说/bin/csh脚本,/bin/perl脚本,/bin/awk脚本,/bin/sed脚本,甚至/bin/echo等等
xxxxxxxxxx
11sh -x some.sh
if 、case、while、
xxxxxxxxxx
1if condition; then
2 statement(s)
3fi
4# 请注意 condition 后边的分号;,当 if 和 then 位于同一行的时候,这个分号是必须的,否则会有语法错误
5# 注意条件闭合,结尾if/fi匹配。
true/false
x
1(( $a > $b ))
2
3(( $a == $b ))
4
5(( $age <= 2 ))
x1-eq: 测试两个整数是否相等;比如 $A -eq $B
2
3-ne: 测试两个整数是否不等;不等,为真;相等,为假;
4
5-gt: 测试一个数是否大于另一个数;大于,为真;否则,为假;
6
7-lt: 测试一个数是否小于另一个数;小于,为真;否则,为假;
8
9-ge: 大于或等于
10
11-le:小于或等于
||、&&、!
注意区分:单个管道|表示的是输出重定向。
条件可以判断结果,也可以是命令执行结果的返回,或者只是激活执行命令。详见正文示例
x
101(( $age > 18 && $iq < 60 ))
2# 同真为真,一假即假。
3# 第一个条件为假时,第二条件不用再判断,此时为假;第一个条件为真时,第二条件必须得判断,此时真假与第二个条件的真假一致。
4
5(( $age > 18 || $iq < 60 ))
6# 或:一真即真
7# ||则与&&相反。如果||左边的命令(command1)未执行成功,那么就执行||右边的命令(command2)
8
9(( ! $age > 18 ))
10# 非:优先级最低,对结果取反。
实用的用法,加深理解
xxxxxxxxxx
131! id user6 && useradd user6
2id user6 || useradd user6
3# 结果等价:用户user6不存在时,执行创建用户user6。这个可以当作简版的if条件语句。
4
5#如果用户不存在,就添加;否则,显示其已经存在;
6! id user1 && useradd user1 || echo "user1 exists."
7
8
9#如果用户不存在,添加并且给密码;否则,显示其已经存在;
10! id user1 && useradd user1 && echo "user1" | passwd --stdin user1 || echo "user1exists."
如果希望把几个命令合在一起执行,shell提供了两种方法: ()运算符 和 {}运算符 。既可以在当前shell也可以在子shell中执行一组命令。
并且注意此时将出现变量作用域的切换。
121[root@666 ~]# echo $SHELL
2/bin/bash
3[root@666 ~]# a=1 ; echo $a ;( a=2;echo $a );echo $a
41
52
61
7[root@666 ~]# a=1 ; echo $a ;{ a=2 ; echo $a; };echo $a;
81
92
102
11[root@666 ~]#
12
11(command1;command2;command3....) #多个命令之间用;分隔
- 一条命令需要独占一个物理行,如果需要将多条命令放在同一行,命令之间使用命令分隔符(;)分隔。执行的效果等同于多个独立的命令单独执行的效果。
- () 表示在当前 shell 中将多个命令作为一个整体执行。需要注意的是,使用 () 括起来的命令在执行前面都不会切换当前工作目录,也就是说命令组合都是在当前工作目录下被执行的,尽管命令中有切换目录的命令。
- 命令组合常和命令执行控制结合起来使用。
11{ command1;command2;command3… } #注意:在使用{}时,{}与命令之间必须使用一个空格
如果使用{}来代替(),那么相应的命令将在子shell而不是当前shell中作为一个整体被执行,只有在{}中所有命令的输出作为一个整体被重定向时,其中的命令才被放到子shell中执行,否则在当前shell执行。
了解相关知识可以减少语法错误,增加排错调试速度。
cmd
效果相同,结果为shell命令cmd的输,过某些Shell版本不支持$()形式的命令替换, 如tcsh。exprexpression
效果相同, 计算数学表达式exp的数值, 其中exp只要符合C语言的运算规则即可, 甚至三目运算符和逻辑表达式都可以计算。71$(cmd)和`cmd`的作用是相同的,在执行一条命令时,会先将其中的 ``,或者是$() 中的语句当作命令执行一遍,再将结果加入到原命令中重新执行
2[root@666 ~]# date=`date -d '1 day ago' "+%Y-%m-%d"`
3[root@666 ~]# echo $date
42021-03-18
5[root@666 ~]# echo `date -d '1 day ago' "+%Y-%m-%d"`
62021-03-18
7
- 整数扩展。这种扩展计算是整数型的计算,不支持浮点型。((exp))结构扩展并计算一个算术表达式的值,如果表达式的结果为0,那么返回的退出状态码为1,或者 是"假",而一个非零值的表达式所返回的退出状态码将为0,或者是"true"。若是逻辑判断,表达式exp为真则为1,假则为0。
- 只要括号中的运算符、表达式符合C语言运算规则,都可用在$((exp))中,甚至是三目运算符。作不同进位(如二进制、八进制、十六进制)运算时,输出结果全都自动转化成了十进制。如:echo $((16#5f)) 结果为95 (16进位转十进制)
- 单纯用 (( )) 也可重定义变量值,比如 a=5; ((a++)) 可将 $a 重定义为6
- 常用于算术运算比较,双括号中的变量可以不使用$符号前缀。括号内支持多个表达式用逗号分开。 只要括号中的表达式符合C语言运算规则,比如可以直接使用for((i=0;i<5;i++)), 如果不使用双括号, 则为for i in
seq 0 4
或者for i in {0..4}。再如可以直接使用if (($i<5)), 如果不使用双括号, 则为if [ $i -lt 5 ]。
- bash 的内部命令,[和test是等同的。如果我们不用绝对路径指明,通常我们用的都是bash自带的命令。if/test结构中的左中括号是调用test的命令标识,右中括号是关闭条件判断的。这个命令把它的参数作为比较表达式或者作为文件测试,并且根据比较的结果来返回一个退出状态码。if/test结构中并不是必须右中括号,但是新版的Bash中要求必须这样。
- Test和[]中可用的比较运算符只有==和!=,两者都是用于字符串比较的,不可用于整数比较,整数比较只能使用-eq,-gt这种形式。无论是字符串比较还是整数比较都不支持大于号小于号。如果实在想用,对于字符串比较可以使用转义形式,如果比较"ab"和"bc":[ ab < bc ],结果为真,也就是返回状态为0。[ ]中的逻辑与和逻辑或使用-a 和-o 表示。
- 字符范围。用作正则表达式的一部分,描述一个匹配的字符范围。作为test用途的中括号内不能使用正则。
- 在一个array 结构的上下文中,中括号用来引用数组中每个元素的编号。
- [[是 bash 程序语言的关键字。并不是一个命令,[[ ]] 结构比[ ]结构更加通用。在[[和]]之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换。
- 支持字符串的模式匹配,使用=~操作符时甚至支持shell的正则表达式。字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串,比如[[ hello == hell? ]],结果为真。[[ ]] 中匹配字符串或通配符,不需要引号。
- 使用[[ ... ]]条件判断结构,而不是[ ... ],能够防止脚本中的许多逻辑错误。比如,&&、||、<和> 操作符能够正常存在于[[ ]]条件判断结构中,但是如果出现在[ ]结构中的话,会报错。比如可以直接使用if [[ $a != 1 && $a != 2 ]], 如果不适用双括号, 则为if [ $a -ne 1] && [ $a != 2 ]或者if [ $a -ne 1 -a $a != 2 ]。
- bash把双中括号中的表达式看作一个单独的元素,并返回一个退出状态码。。
101if ($i<5)
2if [ $i -lt 5 ]
3if [ $a -ne 1 -a $a != 2 ]
4if [ $a -ne 1] && [ $a != 2 ]
5if [[ $a != 1 && $a != 2 ]]
6
7for i in $(seq 0 4);do echo $i;done
8for i in `seq 0 4`;do echo $i;done
9for ((i=0;i<5;i++));do echo $i;done
10for i in {0..4};do echo $i;done
注意${var}是非常标准的变量引用。特别是变量拼接时的用法。
x
1[root@666 ~]# var1=abc;var2=123;var3=ZAWS
2[root@666 ~]# echo aaa_${var1}
3aaa_abc
4[root@666 ~]# echo aaa_${var1}_${var2}
5aaa_abc_123
6[root@666 ~]# echo aaa_${var1}_${var2:2}
7# ${var2:2} 用法说明见下文
8aaa_abc_3
9[root@666 ~]# echo aaa_${var1}_${var2:2}.${var3}
10aaa_abc_3.ZAWS
11[root@666 ~]#
12
以下是深入处理$var变量
61# ls {ex1,ex2}.sh
2ex1.sh ex2.sh
3# ls {ex{1..3},ex4}.sh
4ex1.sh ex2.sh ex3.sh ex4.sh
5# ls {ex[1-3],ex4}.sh
6ex1.sh ex2.sh ex3.sh ex4.sh
41${var:-string}
2${var:+string}
3${var:=string}
4${var:?string}
1、${var:-string}和${var:=string}:若变量var为空,则用在命令行中用string来替换${var:-string},否则变量var不为空时,则用变量var的值来替换${var:-string};对于${var:=string}的替换规则和${var:-string}是一样的,所不同之处是${var:=string}若var为空时,用string替换${var:=string}的同时,把string赋给变量var: ${var:=string}很常用的一种用法是,判断某个变量是否赋值,没有的话则给它赋上一个默认值。
2、${var:+string}的替换规则和上面的相反,即只有当var不是空的时候才替换成string,若var为空时则不替换或者说是替换成变量 var的值,即空值。(因为变量var此时为空,所以这两种说法是等价的)
3、{var:?string}替换规则为:若变量var不为空,则用变量var的值来替换${var:?string};若变量var为空,则把string输出到标准错误中,并从脚本中退出。我们可利用此特性来检查是否设置了变量的值。
补充扩展:在上面这五种替换结构中string不一定是常值的,可用另外一个变量的值或是一种命令的输出。
本质是将变量引用后,再进一步对变量结果处理后,再输出,可以认识有一定的简化作用。
模式匹配记忆方法:
xxxxxxxxxx
21# 是去掉左边(在键盘上#在$之左边)
2% 是去掉右边(在键盘上%在$之右边)
#和%中的单一符号是最小匹配,两个相同符号是最大匹配。
41${var%pattern}
2${var%%pattern
3${var#pattern}
4${var##pattern}
191# var=testcase
2# echo $var
3testcase
4# echo ${var%s*e}
5testca
6# echo $var
7testcase
8# echo ${var%%s*e}
9te
10# echo ${var#?e}
11stcase
12# echo ${var##?e}
13stcase
14# echo ${var##*e}
15
16# echo ${var##*s}
17e
18# echo ${var##test}
19case
xxxxxxxxxx
41${var:num}
2${var:num1:num2}
3${var/pattern/pattern}
4${var//pattern/pattern}
311[root@666 ~]# abc=0123456789
2[root@666 ~]# echo ${abc:2}
323456789
4[root@666 ~]# echo ${abc:4}
5456789
6[root@666 ~]# echo ${abc:8}
789
8[root@666 ~]# echo ${abc:1-8}
93456789
10[root@666 ~]# echo ${abc:7-8}
119
12[root@666 ~]# echo ${abc:-8}
130123456789
14[root@666 ~]# echo ${abc:-2}
150123456789
16[root@666 ~]# echo ${abc:-9}
170123456789
18[root@666 ~]# echo ${abc:-3}
190123456789
20[root@666 ~]# echo ${abc:(-3)}
21789
22[root@666 ~]# echo ${abc:(-2)}
2389
24[root@666 ~]# echo ${abc:(-8)}
2523456789
26[root@666 ~]# echo ${abc:(-9)}
27123456789
28[root@666 ~]# echo ${abc:(-10)}
290123456789
30[root@666 ~]#
31