本文介绍是学习 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.1 数组
用圆括号()
定义,元素之间用空格分开:
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 oprator var_b
,常见整数运算符:
运算符 | 表示方式 |
---|---|
整数加法 | + |
整数减法 | - |
整数乘法 | \* |
整数求模 | / |
整数取余数 | % |
左括号 | \( |
右括号 | \) |
- 1 求模就是整数除法,结果是整数,直接去掉后面的小数
expr -5 / -3 # 1 expr -5 / -3 # -1 expr 5 / -3 # -1 expr 5 / 3 # 1
- 2 求余数以第一个变量符号为准
expr 5 % 3 # 2 expr 5 % -3 # 2 expr -5 % -3 # -2 expr -5 % 3 # -2
- 3 括号用于改变运算优先级,变量之间要添加空格
expr \( 5 + 3 \) \* 2
- 4 叠加
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
5 数组运算
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 中变量定义和的运算:包含了字符串的运算和数组的运算,字符串的运算中包括判断是否相等,整数的四则运算,浮点数四则运算;数组的运算包含了数组的增删查改。