本文是学习 Shell 的第一节,主要介绍 Shell 变量的定义与运算:
- 变量名和变量类型
echo命令和转义字符- 字符串在单引号和双引号下表现不同
{}用于限定字符串的范围- 关键字
unset和readonly
1. 变量定义
Shell 中变量定义如下:
var_name=variable
其中,var_name 是变量名,variable 是变量值。变量名和变量值之间的 = 左右一定不能有空格!
1.1 变量名
Shell 变量名需满足如下规则:
- 只能是字母、数字、下划线,不能数字开头
- 大小写敏感
- 不要包含 Shell 关键字(可用
help命令获取)
根据以上规则,合法的变量名:
hello
hello000
hello_world_00
_konichiwa
nihao_
非法变量名:
help # 关键字
ni hao # 有空格
9am # 数字开头
1.2 变量值
Shell 中变量值分字符串和数组两种。
变量值来源有直接赋值和来自命令输出两种。
1.2.1 字符串
字符串是 Shell 中常见的数据类型。Shell 中没有 int bool 各种类型。除了数组(下面会介绍)之外都是字符串。
下面所有变量都是字符串:
a="xy"
b=1
c="1"
d="hello world"
e="x\n"
f='x'
g='x\n'
在定义字符串的值时,可以添加引号来显式确定范围,实际结果中不会包含这个引号。需要注意单引号和双引号的区别(下面会介绍)。
a="xy"
a='xy'
a=xy
上面三个定义中 a 的值都是 xy(不带引号)
当然,也可以用其他命令输出作为变量值,用 `cmd`(反引号,esc 下面那个键)或者 $(cmd) 的方式:
x=`pwd`
x=$(pwd)
1.2.2 数组
用圆括号 () 定义,元素之间用空格分开:
var_list=(1 2 3)
Shell 中的数组类似 Python 中的 dict,有 key 和 value 之分,只是恰好这个 key 是整数。所以也可以如下定义数组:
var_list=(0=[hhh] 1=[2])
数组的元素是下面介绍的字符串。Shell 只支持一维数组,不支持高维数组。
当然,Shell 中数组也可以来自命令输出,用 `cmd`(反引号,esc 下面那个键)或者 $(cmd) 的方式:
a=`ls -a`
b=$(ls -a)
echo 'files are `ls -a`'
echo "files are `ls -a`"
echo "files are $(ls -a)"
1.3 变量引用
定义一个变量后,在其他地方要使用这个变量的值。
1.3.1 echo 命令
在介绍 Shell 中变量引用之前,先介绍 Shell 中的 echo 命令,它的作用是打印变量值。(类似 Python 中的 print 和 C++ 中的 printf)。
echo "haha" # haha
echo 命令也有参数:加上 -e 则有转义,加上 -E 参数则没有转义(默认无转义)。e 是 escape 的意思。Shell 中转义字符如下:
| 转义字符 | 含义 |
|---|---|
\\ | 反斜杠 |
\a | 警报,响铃 |
\b | 退格(删除前面一个字符) |
\f | 换页 (FF),将当前位置移到下页开头 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符(tab 键) |
\v | 垂直制表符 |
关于 \n 和 \r 的区别,可以参考另外一篇文章。
echo -e "x\n"
# x
#
echo -E "\n"
# \n
1.3.2 变量引用
- 引用已经定义好的变量:在变量名前面添加
$(定义变量或变量重新赋值时不加$) - 用
$获取变量时候,可添加{}来显式界定变量范围
为说明这个问题,先介绍 Shell 中字符串的拼接操作,将两个字符串写在一起。
a=5
b=6
echo a # output a
echo $a$b # output 56
echo $a # output 5
echo ${a}x # output 5x
echo $ax # output None(no output)
大括号 {} 用来界定范围,${a}x 界定变量是 a 而不是 ax。
Shell 中变量可以用引号括起来,实际内容不会包含引号。其中单双引号并不能通用:
- 单引号中的内容,也不会计算变量值,是啥样就是啥样
- 双引号中的内容,会先计算变量结果,然后输出
a=5
echo 'a' # output a
echo "a" # output a
echo '$a' # output $a
echo "$a" # output 5
echo $a # output 5
1.3.3 变量替换
Shell 中对于没有定义的变量强行 echo,则会啥也不输出,也不会报错。 为解决变量没有定义造成的问题,Shell 中有变量替换的语法,用于处理变量没有赋值时候的场景。
| 形式 | 说明 |
|---|---|
${var} | var 的值 |
${var:-word} | var 若被定义,则输出 var 的值;var 若为空,或者未定义或已被删除 (unset),返回 word,但不改变 var 的值。 |
${var:=word} | var 若被定义,则输出 var 的值;var 若为空或已被删除 (unset),返回 word,并将 var 的值设置为 word。 |
${var:?message} | var 若被定义,则输出 var 的值;var 若为空或已被删除 (unset),消息 message 送到标准错误输出。该操作可以用来检测变量 var 是否可以被正常赋值。若此替换出现在 shell script 中,则 shell script 将停止执行。 |
${var:+word} | var 若被定义,那么返回 word,但不改变 var 的值。如果 var 没有被定义,则返回空。 |
1.4 变量 unset 和 readonly
Shell 中定义一个变量后,可以通过 unset 的方式删掉。
a=4
echo $a # output 4
unset a
echo $a # output None(no output)
在 Shell 中如果一个变量被删除掉,再引用则会得到 None 的返回。这和大多数编程语言不一样,大多数编程语言在处理没有定义的变量时会直接报错。(Shell 这种机制也增加了 Shell script 的调试难度。)
定义变量时候添加 readonly 关键字:
readonly x=5
x=6 # readonly variable
变量前添加 readonly 之后,是无法删除掉的。
2. 字符串运算
Shell 变量都是数组和字符串。对于变量,除了获取变量的值之外,还可以进行更多操作。常见的操作有:
- 字符串求长度
- 字符串拼接
- 字符串截取
- 字符串比较
- 数字字符串的运算
2.1 字符串求长度
x="hello world"
echo ${#x} # 11
# 是 shell 中表示注释的符号,如果在一个变量前面,左右又被 {} 包围,则是求长度的意思。
2.2 字符串拼接
就是将多个字符串合并为一个字符串,直接排列即可。
x=5
y='u'
z=$x$y
echo $z # 5u
z=${x}${y}
echo $z # 5u
2.3 字符串截取
从原来的字符串中按照一定规则取出来一部分。
2.3.1 按长度截取
对变量 x 进行截取:
${x:start:length}
- 从左往右截取
start是开始的 index- Shell 中数组最开始元素的 index 是 0
start可以是一个数,表示是开始的 index。start也可以是0-n样子,则是从后往前找开始的字符
- 如果
length长度超出了整个字符串结尾,则截取start开始的所有字符
x="hello world"
y=${x:1:2} # 1 is the start index left to right, 2 is the length
echo $y # el
y=${x:0-5:2} # right to left
echo $y # wo
2.3.2 按特定字符截取
#截取右边字符,%截取左边字符- 一个
#(或%)表示从左往右第一个,两个表示从左往右最后一个 - 用
*表示任意字符
# 使用#截取从左往右的右边的字符
url="http://aoi.ai/index.html"
echo ${url#*/} # /aoi.ai/index.html,从左往右第一个右边的字符
echo ${url##*/} # index.html,从左往右最后一个右边的字符
str="---aa+++aa@@@"
echo ${str#*aa} # +++aa@@@
echo ${str##*aa} # @@@
# 使用%截取从左往右左边的字符
url="https://aoi.ai/index.html"
echo ${url%/*} # https://aoi.ai
echo ${url%%/*} # http:
str="---aa+++aa@@@"
echo ${str%aa*} # ---aa+++
echo ${str%%aa*} # ---
2.4 字符串比较
2.4.1 test 和 if 函数
可以在 Shell 中对字符串进行比较。判断的结果可以传递给 if 等函数。例子如下:
# use test function
if test "$a" = "$b";then
command_1
else
command_2
fi
# use [ ]
if [ "$a" = "$b" ]; then
command_1
else
command_2
fi
分号 ; 并非必要。test "$a" = "$b" 就是用于比较的意思,比较的结果传递给 if 函数,如果 test 中的结果为真,则执行 command_1,否则执行 command_2。在实际使用中 test 可以用中括号 [] 来表示。
2.4.2 字符串相等与不等
如果两个字符串完全一样,则是相等;如果不一样则不等。判断相等用 =(左右有空格),判断不等用 !=(左右有空格)。
a=5
b=5
if [ $a = $b ]; then
echo "equal"
fi
在实际使用中,还会遇到双等号(==)情况:
- 在 bash 中,单等号和双等号是等效的
- 在其他 shell 中,则单等号表示相等
所以推荐的写法是用单等号。
2.4.3 数字字符串操作
在 Shell 中,虽然没有特别的数字类型数据,但是如果字符串符合整数形式,则可以按照数字来处理:除了可以比较相等与否,还可以比较大小:
| 含义 | 表示方式 |
|---|---|
| equal 等于 | -eq |
| not equal 不等于 | -ne |
| greater than 大于 | -gt |
| less than 小于 | -lt |
| greater equal 大于等于 | -ge |
| less equal 小于等于 | -le |
需要注意的是,这些运算左右一定要是整数,否则就会报错。
a=3
b=2
if [ $a -eq $b ]; then
echo "$a equals $b"
else
echo "$a does not eqaul $b"
fi
2.4.4 数字四则运算
Shell 中没有数字这种类型,数字也被当成字符串处理。如果非要进行数字四则运算,则需要借助额外的工具。
整数运算
使用 expr 函数,规则是 expr var_a operator var_b,常见整数运算符:
| 运算符 | 表示方式 |
|---|---|
| 整数加法 | + |
| 整数减法 | - |
| 整数乘法 | \* |
| 整数除法 | / |
| 整数取余数 | % |
| 左括号 | \( |
| 右括号 | \) |
-
- 除法就是整数除法,结果是整数,直接去掉后面的小数
expr -5 / -3 # 1 expr -5 / -3 # -1 expr 5 / -3 # -1 expr 5 / 3 # 1 -
- 求余数以第一个变量符号为准
expr 5 % 3 # 2 expr 5 % -3 # 2 expr -5 % -3 # -2 expr -5 % 3 # -2 -
- 括号用于改变运算优先顺序,变量之间要添加空格
expr \( 5 + 3 \) \* 2 -
- 叠加
expr运算符,用于连续计算
expr $(expr 4 + 5) \* 8 # 72 expr `expr 4 + 5` \* 8 # 72 - 叠加
浮点运算
浮点运算要用 bc:
echo 5.5 + 5.5 + 5.5 | bc # 16.5
echo 5.5 \* \( 5.5 + 5.5 \) | bc # 60.5
其中 | 是管道命令,将前一个命令的输出作为后一个命令的输入。添加 scale 可以设置保留的位数。
echo 5.5 / 10 | bc # 0
echo "scale=4; 5.5 / 10" | bc # 0.5500
3. 数组运算
3.1 数组访问
3.1.1 访问某一个元素
用 ${a[index]},Shell 中数组元素 index 从 0 开始。
a=(1 2 3)
echo "${a[2]}" # 3
index 可以是正整数,表示从左往右数的第几个数。如果 index 指代的索引不存在,则返回空。
a=(1 2 3)
echo "${a[4]}" # 输出一行空白,表示没有
当然,也可以像字符串截取一样,使用 0-n 的形式,表示从右到左,此时从 1 开始记数:
a=(1 2 3)
echo "${a[0-1]}" # 3
3.1.2 数组遍历
要同时输出数组中的所有元素,可以用 @ 或者 *:
a=(1 3 5)
echo "${a[@]}" # 1 3 5
echo "${a[*]}" # 1 3 5
Shell 中还有一个用于遍历访问的函数 for:
for i in ${a[@]};
do
command_1
done
其中 a 是一个数组。in 后面的对象不能是数组,而要用 ${a[@]} 或者 ${a[*]} 的形式:
a=(1 2 3)
for i in ${a[@]};
do
echo $i
done
# 1
# 2
# 3
for i in ${a[*]};
do
echo $i
done
# 1
# 2
# 3
for i in $a;
do
echo $i
done
# 1
$a[*] 和 $a[@] 都表示数组中所有元素,它们有什么区别呢:
- 不被双引号(” “)包含时,都以
"$1" "$2" … "$n"的形式输出所有参数。 - 当它们被双引号(” “)包含时:
"$a[*]"会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数"$a[@]"会将各个参数分开,以"$1""$2"…"$n"的形式输出所有参数
a=(1 2 3)
for i in "${a[@]}";
do
echo $i
done
# 1
# 2
# 3
for i in "${a[*]}";
do
echo $i
done
# 1 2 3
借助 * 和 @ 符号,可以获得数组中所有元素。和获取字符串长度类似,也可以获得整个数组中的元素个数:
a=(1 2 3)
echo "${#a[@]}"
3.1.3 数组截取
意思是说取得数组的部分,类似字符串访问:
a=(1 2 3)
echo "${a[@]:1:2}" # 2 3
echo "${a[@]:0-1:2}" # 3
3.2 数组修改
修改数组中某一个元素,用赋值的方式去修改(不要添加美元符号)。
a=(1 2 3)
echo "${a[1]}" # 2
a[1]=4
echo "${a[1]}" # 4
往数组后面添加一个元素,只需要将 index 设置为数组长度后面一个就行。
a=(1 2 3)
echo "${#a[@]}" # 3
a[3]=4
echo "${#a[@]}" # 4
a[8]=6
echo "${#a[@]}" # 5
这个 index 可以是任意正数。数组 a 本身长度为 3,此时可以设置 a[3] 的值,也可以设置 a[5] 的值。这个时候,数组又有点类似 Python 的字典了,其中 key 就是 index,如果 index 不存在则返回空。虽然强行添加了 a[8],但是整个数组的长度还是 5。
3.3 数组删除
使用 unset
a=(1 2 3)
unset a[0]
echo "${a[0]}" # 空白
3.4 数组元素替换
数组中的元素也可以替换,替换的是 value,不是 key。而且不会修改原来数组。
a=(1 2 3)
echo "${a[@]/3/777}" # 1 2 777
echo "${a[@]}" # 1 2 3
总结
本文介绍了 Shell 中变量定义和运算:包含了字符串的运算和数组的运算,字符串的运算中包括判断是否相等,整数的四则运算,浮点数四则运算;数组的运算包含了数组的增删查改。