Shell Tutorial 01

Variables in Shell, string and array, echo command, variable reference. Modified: 2023-10-27 20:01:58 Created: 2021-08-22 09:50:08 Tags: #linux #shell

本文介绍是学习 Shell 第一节,主要介绍 Shell 变量的定义与运算:

  • 变量名和变量类型
  • echo命令和转义字符
  • 字符串在单引号和双引号下表现不同
  • {}用于限定字符串的范围
  • 关键字unsetreadonly

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 变量unsetreadonly

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 testif函数

可以在 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 中数组元素index0开始。

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 中变量定义和的运算:包含了字符串的运算和数组的运算,字符串的运算中包括判断是否相等,整数的四则运算,浮点数四则运算;数组的运算包含了数组的增删查改。