Bash 2 变量操作
Table of Contents
1 空值判断
Bash 提供四个特殊语法,跟变量的值有关,目的是保证变量不为空。
${varname:-word}
如果变量 varname
存在且不为空,则返回它的值,否则返回 word
。它的目的是 返回一个默认值,并保留原始值 ( 空值 ) 。比如 ${count:-0}
表示变量 count
不存在时返回默认值 0。
${varname:=word}
如果变量 varname
存在且不为空,则返回它的值,否则将它设为 word
,并且返回 word
。它的目的是 设置变量的默认值,并将其返回 。比如 ${count:=0}
表示变量 count
不存在时返回 0,且将 count
值设为 0。
${varname:+word}
如果变量 varname
存在且不为空,则返回 word
,否则返回空值。它的目的是 测试变量是否存在 。比如 ${count:+1}
表示变量 count
存在时返回 1 ( 表示 true ),否则返回空值。
${varname:?message}
如果变量 varname
存在且不为空,则返回它的值,否则打印出 varname: message
,并中断脚本的执行。如果省略 message
,则输出默认的信息 parameter null or not set.
。它的目的是 防止变量未定义 。比如 ${count:?"undefined!"}
表示变量 count
未定义时就中断执行,抛出错误,返回给定的报错信息 undefined!
。
上面四种语法如果用在脚本中,变量名的部分可以用数字1到9,表示脚本的参数。比如:
# 1 表示脚本的第一个参数。如果该参数不存在,就退出脚本并报错。
filename=${1:?"filename missing."}
2 变量运算
number
: 没有任何特殊表示法的数字是十进制数。0number
: 八进制数。0xnumber
: 十六进制数。base#number
: base进制的数。
不论运算结果是否符合逻辑,只要运算结果为 0 即为运算命令执行失败。
((1-1))
最后执行的是算数减法。运算结果为 0 即运算失败。
((var=1-1))
最后执行的是赋值运算,即 ((var=0))
。赋值运算总是能执行成功。
赋值语句返回等号右边的值,即赋 0 值可以正常执行,但赋 0 值的返回值为 0。比如 if ((var=0))
等于 if [[ 0 ]]
,为 false。
2.1 整数运算
2.1.1 ((...)) 语法
((...)) 会自动忽略内部的空格,进行 整数算术运算 。它支持:
+
: 加法。-
: 减法。*
: 乘法。/
: 除法 ( 整除 )。除法运算符的返回结果总是整数,比如 5 除以 2 得 2,不是 2.5。%
: 余数。**
: 指数。++
: 自增运算 ( 前缀或后缀 )。--
: 自减运算 ( 前缀或后缀 )。
++
和 --
这两个运算符有前缀和后缀的区别。作为前缀,先运算后返回值。作为后缀,先返回值后运算。
赋值必须发生在 ((...)) 内部。
$ ((foo=5+5)) $ echo $foo 10
((...)) 内部可以用圆括号改变运算顺序,也可以嵌套。
$ (( var1 = (2 + 3) * 4 )) $ echo var1 20 $ (( var2 = ((2 + 3)) * 4 )) $ echo var2 20
((...)) 内的字符串按变量名处理。如果变量不存在,按空值 ( 数字 0 ) 处理,不报错。但如果字符串是数字,按数字字面量处理。
$ (( var1 = "hello" + 2)) $ echo $var1 2 $ (( var2 = "hello" * 2)) $ echo $var2 0 $ ((var3 = "22" + 2)) $ echo $var3 24 $ ((var4 = "22" * 2)) $ echo $var4 44
2.1.2 $((...)) 语法
如果 ((...)) 内有变量或赋值发生在 ((...)) 外,需写成 $((...))。
$ foo=$((5+5)) $ echo $foo 10 $ bar=$((foo+5)) $ echo $bar 15
$((...)) 内部可以用圆括号改变运算顺序,也可以嵌套。
$ echo $(( (2 + 3) * 4 )) 20 $ echo $(( $((2 + 3)) * 4)) 20
2.2 浮点运算
很可惜,Bash 莫得浮点运算。 echo $((5/2))
最后得 2。
讲道理,真有这个需要,用 Python 去吧。
要非得用, sudo apt install bc
装个计算器凑合用。
value=`bc<<EOF # 在反引号中使用here string的方式
scale=3
r=3
3.14159*r*r
EOF`
area=`echo "scale=2;r=3;3.14159*r*r"|bc`
但这个姿势用起来有点痛苦。
2.3 逻辑运算
$((...)) 支持以下的逻辑运算符。
<
: 小于。>
: 大于。<=
: 小于或相等。>=
: 大于或相等。==
: 相等。!=
: 不相等。&&
: 逻辑与。||
: 逻辑或。!
: 逻辑否。expr1?expr2:expr3
: 三元条件运算语法。若expr1
的计算结果为非零值(算术真),则执行expr2
,否则执行expr3
。
如果逻辑表达式为真,返回 1,否则返回 0。
$ echo $((3 < 2)) 0 $ echo $((3 > 2)) 1 $ echo $(( (3 > 2) || (4 <= 1) )) 1
三元运算符执行一个单独的逻辑测试。它用起来类似于 if/then/else 语句。
$ a=0 $ echo $((a<1 ? 1 : 0)) 1 $ echo $((a>1 ? 1 : 0)) 0
2.4 位运算
$((...)) 支持以下的二进制位运算符:
<<
: 位左移运算,数字的所有位向左移动指定的位。>>
: 位右移运算,数字的所有位向右移动指定的位。&
: 位的 与 运算,对两个数字的所有位执行 AND 操作。|
: 位的 或 运算,对两个数字的所有位执行 OR 操作。~
: 位的 否 运算,对一个数字的所有位取反。^
: 位的异或运算 ( exclusive or ),对两个数字的所有位执行异或操作。
下面是右移运算符 >>
的例子:
$ echo $((16>>2)) 4
下面是左移运算符 <<
的例子:
$ echo $((16<<2)) 64
下面是 17 ( 二进制 10001 ) 和 3 ( 二进制 11 ) 的各种二进制运算的结果。
$ echo $((17&3)) 1 $ echo $((17|3)) 19 $ echo $((17^3)) 18
2.5 赋值运算
将值赋给变量的运算,如 $((a=1))
即为赋值运算。返回值为等号右边的值。
((...)) 支持的赋值运算符有:
parameter = value
: 简单赋值。parameter += value
: 等价于parameter = parameter + value
parameter -= value
: 等价于parameter = parameter – value
parameter *= value
: 等价于parameter = parameter * value
parameter /= value
: 等价于parameter = parameter / value
parameter %= value
: 等价于parameter = parameter % value
parameter <<= value
: 等价于parameter = parameter << value
parameter >>= value
: 等价于parameter = parameter >> value
parameter &= value
: 等价于parameter = parameter & value
parameter |= value
: 等价于parameter = parameter | value
parameter ^= value
: 等价于parameter = parameter ^ value
2.6 求值运算
逗号 ,
在 $((...)) 内部是求值运算符,执行前后两个表达式,并返回后一个表达式的值。
$ echo ((foo = 1 + 2, 3 * 4)) 12 $ echo $foo 3
上面例子中,逗号前后两个表达式都会执行,然后返回后一个表达式的值 12。
3 字符串操作
3.1 截取子串
返回变量 $varname
的子字符串,从位置 offset
开始 ( 从 0 开始计算 ),长度为 length
。
${varname:offset:length}
$ count=frogfootman $ echo ${count:4:4} foot
这种语法不能直接操作字符串,只能通过变量来读取字符串,并且不会改变原始字符串。
# 报错,"hello" 不是变量名
echo ${"hello":2:3}
如果省略 length
则从位置 offset
开始,一直返回到字符串的结尾。
$ count=frogfootman $ echo ${count:4} footman
offset
为负值表示从字符串的末尾开始算起。负数前面必须有一个空格,以防止与 ${variable:-word}
设置变量默认值的语法混淆。这时指定的 length
可以是是负值,表示排除从字符串末尾开始的 length
个字符。
$ foo="This string is long." $ echo ${foo: -5} long. $ echo ${foo: -5:2} lo $ echo ${foo: -5:-2} lon
3.2 搜索和替换
不论结果如何,原变量不变。
匹配成功后删除匹配到的部分:
${variable/pattern}
匹配整个串。非贪婪匹配。${variable//pattern}
匹配整个串。贪婪匹配。${variable#pattern}
从头部开始匹配。非贪婪匹配。${variable##pattern}
从头部开始匹配。贪婪匹配。${variable%pattern}
从尾部开始匹配。非贪婪匹配。${variable%%pattern}
从尾部开始匹配。贪婪匹配。
匹配成功后替换匹配到的部分 ( 指定开始位置的替换模式没有贪婪匹配 ):
${variable/pattern/string}
匹配整个串。非贪婪匹配。${variable//pattern/string}
匹配整个串。贪婪匹配。${variable/#pattern/string}
从头部开始匹配。非贪婪匹配。${variable/%pattern/string}
从尾部开始匹配。非贪婪匹配。
匹配模式 pattern
可以使用 *
?
[]
等通配符。
$ myPath=/home/cam/book/long.file.name $ echo ${myPath#/*/} cam/book/long.file.name $ echo ${myPath##/*/} long.file.name
下面写法可以删除文件路径的目录部分,只留下文件名。
$ path=/home/cam/book/long.file.name $ echo ${path##*/} long.file.name
3.3 改变大小写
# 转为大写
${varname^^}
# 转为小写
${varname,,}
下面是一个例子。
$ foo=heLLo $ echo ${foo^^} HELLO $ echo ${foo,,} hello
3.4 Here document
Here 文档 ( Here document ) 是一种输入多行字符串的方法。格式如下:
<< token text token
它由开始标记 << token~、结束标记 ~token
和文本正文组成。开始标记是 <<
后接 Here 文档的名称,名称可以随意取,后面必须是一个换行符。结束标记是单独一行、顶格写的 Here 文档名称,如果不是顶格,结束标记不起作用。
给个例子:
$ cat << _EOF_
<html>
<head>
<title>
The title of your page
</title>
</head>
<body>
Your page content goes here.
</body>
</html>
_EOF_
Here 文档内部:
- 会发生变量替换。
- 支持反斜杠转义。
- 不支持通配符扩展。
- 单双引号失去语法作用,变成了普通字符。
$ foo='hello world' $ cat << _example_ $foo "$foo" '$foo' _example_ hello world "hello world" 'hello world'
如果不希望发生变量替换,可以把开始标记放在单引号之中:
$ foo='hello world' $ cat << '_example_' $foo "$foo" '$foo' _example_
Here 文档的本质是重定向,它将字符串重定向输出给某个命令,相当于包含了 echo 命令。
command << token string token # 等同于 echo string | command
所以,Here 文档只适合接受标准输入作为参数的命令,对于其他命令无效。比如 echo
不能用 Here 文档作为参数。
此外,Here 文档也不能作为变量的值,只能用于命令的参数。
给两个例子:
- 重定向并覆盖 output_file.txt 文件的内容。
cat <<EOF > output_file.txt
mesg1
msg2
msg3
$var on $foo
EOF
- 定向并追加到 output_file.txt 文件的末尾。
cat <<EOF >> output_file.txt
mesg1
msg2
msg3
$var on $foo
EOF
3.5 Here string
Here 字符串 ( Here string ),作用与 Here 文档相同,都是将字符串通过标准输入,传递给命令。使用 <<<
表示。
<<< string
有些命令处理直接给定的参数,与通过标准输入接受的参数,产生的结果是不一样。所以才有了这个语法。使得将字符串通过标准输入传递给命令更方便。比如 cat 命令只接受标准输入传入的字符串。
$ cat <<< 'hi there' # 等同于 $ echo 'hi there' | cat
md5sum 命令只能接受标准输入作为参数,直接将放在命令后面的字符串会被当作文件名,即 md5sum ddd
中的 ddd
会被解释成文件名。这时就可以用 Here 字符串,将字符串传给 md5sum 命令。
$ md5sum <<< 'ddd' # 等同于 $ echo 'ddd' | md5sum
4 命令
4.1 let
let 命令用于将算术运算的结果赋予变量。可以同时对多个变量赋值。
$ let x=2+3 $ echo $x 5
let命令的参数表达式如果包含空格,就需要使用引号。
$ let "v1 = 1" "v2 = v1++" $ echo $v1,$v2 2,1
4.2 expr
expr 命令可以执行算术运算,可以不使用 ((...)) 语法,且支持变量替换。但不支持非整数参数。
$ foo=3 $ expr $foo + 2 5