LV02-02-shell-02-变量与替换

本文主要是shell——变量与替换相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
参考方向 参考原文
------
点击查看相关文件下载
--- ---

一、 shell 变量

1.变量的定义

shell 允许用户建立变量存储数据,但不支持数据类型(整型、字符、浮点型),将任何赋给变量的值都解释为一串字符。变量定义格式如下(支持三种形式):

1
2
3
Variable=value   # 第一种
Variable='value' # 第二种
Variable="value" # 第三种

【注意】

(1)等号两边不允许有空格

(2)命名只能使用英文字母数字下划线首个字符不能以数字开头,且中间不能有空格。

(3)不能使用标点符号,而且不能使用 bash 里的关键字(可用help命令查看保留关键字)。

(4)第二种与第三种的区别:

以单引号 ‘ ‘ 包围变量的值时,单引号里面是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合定义显示纯字符串的情况,即不希望解析变量、命令等的情况。

以双引号 “ “ 包围变量的值时,输出时会先解析里面的变量和命令,而不是把双引号中的变量名和命令原样输出。这种方式比较适合字符串中附带有变量和命令并且想将其解析后再输出的变量定义。

2.变量的赋值

在 Bash shell 中,每一个变量的值都是字符串,无论给变量赋值时有没有使用引号,值都会以字符串的形式存储。这就意味着, Bash shell 在默认情况下不会区分变量类型,即使将整数和小数赋值给变量,它们也会被视为字符串。

  • 直接赋值
1
Variable=value
  • 用语句赋值
1
2
3
4
5
# 赋值方式1
Variable= command

# 赋值方式2
Variable=$(command)

【注意】

1
用语句进行赋值时,语句一定要写在     符号里边,该符号为英文状态下 Esc 按键下边的哪个按键。或者也可以用 $() 的方式,这种方式看起来要更直观一些。

3.变量的使用

使用一个定义过的变量,只要在变量名前面加美元符号( $ )即可。

1
2
${Variable}
$Variable

【注意】

(1)变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界。推荐给所有变量加上花括号。

(2)定义变量和给变量赋值的时候都不需要加 $ ,使用的时候才需要加上。

4.变量的类型

  • 局部变量:在脚本或命令中定义,仅在当前 shell 实例中有效,其他 shell 启动的程序不能访问局部变量。

  • 环境变量:所有的程序,包括 shell 启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候 shell 脚本也可以定义环境变量。

  • shell 变量:shell 变量是由 shell 程序设置的特殊变量。 shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 shell 的正常运行。

5. Bourne Shell 中的常用的四种变量

5.1用户自定义变量

就是用户自己定义的变量,在 shell 编程中,通常使用全大写,例如

1
COUNT=1

5.2位置变量 

在 Linux 的命令行中,当一条命令或脚本执行时,后面可以跟多个参数,可以使用位置参数变量来表示这些参数。

$n n 为数字,$0 代表命令本身,$1-$9 代表第 1-9 个参数,10 以上的参数需要用大括号{}包含, 例如${10},${15}
$* 代表命令行中所有的参数,把所有的参数看成一个整体
$@ 代表命令行中所有的参数,不过 $@ 把每个参数区别对待
$# 代表命令行中所有参数的个数

【使用实例】

1
2
3
4
5
6
7
hk@ubuntu-22-04:~/1sharedfiles/6temp/myubuntu$ ./1.sh 1 2 3
$*=1 2 3
$#=3
$@=1 2 3
$0=./1.sh
$1=1
$2=2

【注意】

(1) $n 中, n 大于等于 10 时,一定要加上 {} 。

(2) $* 与 $@ :

当 $* 和 $@ 不被双引号 “ “ 包含时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔,两者都以 “$1” “$2” … “\$n” 的形式输出所有参数。

当 $* 和 $@ 被双引号 “ “ 包含时, “$*“ 会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据,以 “$1 $2 … $n” 的形式输出所有参数; “$@” 仍然将每个参数都看作一份数据,彼此之间是独立的,以 “$1” “$2” … “$n” 的形式输出所有参数。

变量 输出形式
被 " " 包含 不被 " " 包含
$* "$1 $2 … $n" "$1" "$2" … "$n"
$@ "$1" "$2" … "$n" "$1" "$2" … "$n"

例如:某命令传递了 5 个参数,那么对于 “$*” 来说,这 5 个参数会合并到一起形成 1 个数据,这一个数据包含了 5 个参数,它们之间是无法分割的;而对于 “$@” 来说,这 5 个参数是相互独立的,它们是 5 个数据。

5.3预定义变量

预定义变量是在 Shell 一开始时就定义的变量,这一点和默认环境变量有些类似。不同的是,预定义变量不能重新定义,用户只能根据 Shell 的定义来使用这些变量。严格来说,位置参数变量也是预定义变量的一种,只是位置参数变量的作用比较统一,所以一般把位置参数变量单独划分为一类变量。

$? 最后一次执行的命令的返回状态。如果这个变量的值为 0,则证明上一条命令正确执行;如果这个变量的值为非0 (具体是哪个数由命令自己来决定),则证明上一条命令执行错误。
$$ 代表当前进程的进程号(PID)。
$! 代表后台运行的最后一个进程的进程号(PID)。

5.4环境变量

环境变量也称为全局变量,可以在创建它们的 Shell 及其派生出来的任意子进程 Shell 中使用,环境变量又可以分为自定义环境变量和 bash 内置的环境变量。

环境变量可以在命令行中设置和创建,用户退出命令行时这些变量值就会丢失,那如何永久保存环境变量呢?可以在用户家目录下的 .bash_profile(或者是.profile) 或 .bashrc(非用户登录模式特有,如:SSH) 文件中,这两个文件设置后是针对特定用户的,也可以在 /etc/profile 文件中定义环境变量,该文件设置后将会影响所有的用户,就是说在这三个文件中进行定义的环境变量在用户登录后就会进行初始化。

例如:下图为 ~/.profile 文件内容。

  • 常用环境变量
HOME /etc/passwd文件中列出的用户主目录
PATH 系统默认的可执行文件搜索路径
HISTSIZE 保留历史命令的数目上限
OSTYPE 系统类型
PWD 当前工作目录
TERM 终端类型,常用的有vt100,ansi,vt200,xterm等
PS1, PS2 默认提示符($)及换行提示符(>)
IFS Internal Field Separator, 默认为空格,tab及换行符

6.字符串 

6.1 使用格式

在 shell 中,字符串可以用单引号,也可以用双引号,也可以不用引号。

  • 单引号

单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的。单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

1
str='string'
  • 双引号

双引号里可以有变量,也可以出现转义字符。

6.2拼接字符串

6.2.1使用单引号拼接

1
2
3
4
5
6
7
#!/bin/bash 
str1='World'

str2='Hello,'$str1' !'
str3='Hello, $str1 !'
echo $str2
echo $str3

输出结果为:

1
2
Hello,World !
Hello, $str1 !

6.2.2使用双引号拼接

1
2
3
4
5
6
7
#!/bin/bash 
str1="World"

str2="Hello, "$str1" !"
str3="Hello, $str1 !"
echo $str2
echo $str3

输出结果为:

1
2
Hello, World !
Hello, World !

6.3获取字符串长度

6.3.1使用格式

1
${#<string>}
  • string :为要获取长度的目标字符串变量。

6.3.2使用实例

1
2
3
4
5
6
#!/bin/bash 
str1='World'
str2="World"

echo "str1's length=${#str1}"
echo "str1's length=${#str2}"

输出结果为:

1
2
str1's length=5
str1's length=5

6.4提取字符串

6.4.1截取方式 1

1
${<string>:n:len}

提取出的字符串为 n 位置处字符极其之后的 len-1 个字符。

  • string :为要获取长度的目标字符串变量。
  • n :为起始字符位置;
  • len :为截取字符串的长度。

【注意】字符串中首个字符的索引为 0 。

【使用实例】

1
2
3
4
5
6
#!/bin/bash 
str1='Hello, World'
str2="Hello, World"

echo "${str1:2:3}"
echo "${str2:2:7}"

输出结果为:

1
2
llo
llo, Wo

6.4.2 截取方式 2

1
2
3
4
${parameter#*word}  # 即从左向右截取第一个word后的字符串
${parameter##*word} # 即从左向右截取最后一个word后的字符串
${parameter%word*} # 即从右向左截取第一个word前的字符串
${parameter%%word*} # 即从右向左截取最后一个word前的字符串

【注意】上边的这些命令截取之后,都是不包含 word 的,所以如果开头或者结尾含有 word 的时候,可能会截取出来一个空的字符串。

【使用实例】

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
FILE_PATH=a/b/c/d/file.tar.gz
echo "\${FILE_PATH} = ${FILE_PATH}"
echo ""
echo "\${FILE_PATH#*/} = ${FILE_PATH#*/}"
echo "\${FILE_PATH##*/} = ${FILE_PATH##*/}"
echo "\${FILE_PATH%/*} = ${FILE_PATH%/*}"
echo "\${FILE_PATH%%/*} = ${FILE_PATH%%/*}"
echo ""
echo "\${FILE_PATH#*.} = ${FILE_PATH#*.}"
echo "\${FILE_PATH##*.} = ${FILE_PATH##*.}"
echo "\${FILE_PATH%.*} = ${FILE_PATH%.*}"
echo "\${FILE_PATH%%.*} = ${FILE_PATH%%.*}"

输出的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
hk@vm:~/1sharedfiles/6temp$ ./1.sh 
${FILE_PATH} = a/b/c/d/file.tar.gz

${FILE_PATH#*/} = b/c/d/file.tar.gz
${FILE_PATH##*/} = file.tar.gz
${FILE_PATH%/*} = a/b/c/d
${FILE_PATH%%/*} = a

${FILE_PATH#*.} = tar.gz
${FILE_PATH##*.} = gz
${FILE_PATH%.*} = a/b/c/d/file.tar
${FILE_PATH%%.*} = a/b/c/d/file

6.5查找字符串中字符

6.5.1使用格式

可以通过 expr 实现字符串中特定字符的查找,这种查找方式会查找字符串中首次出现该字符的位置。

1
Variable= `expr index "${<string>}" <char> `
  • string :为要查找的目标字符串变量。
  • char :为要查找的字符。

【使用实例】

1
2
string="runoob is a great site"
echo `expr index "$string" o` # 输出 4

【注意】

(1)在查找过程中,字符串中首个字符的索引为 1 。

(2)以上脚本中 ` 是反引号,而不是单引号 **’**。

(3) char 可以是多个字符,例如对于 ac , a , c 两个字符哪个先出现,就会停止查找,返回首先出现的字符在目标字符串中的首次出现的位置。

6.5.2使用实例

1
2
3
4
5
6
#!/bin/bash 
str1='Hello, World'
str2="Hello, World"

echo expr index "$str1" ' '
echo expr index "$str2" l

输出结果为:

1
2
7
3

二、 shell 数组 

bash 支持一维数组(不支持多维数组),并且没有限定数组的大小。数组元素的下标由 0 开始。获取数组中的元素要用下标,下标可以是整数或算术表达式,其值应大于或等于0。

1.定义一个数组

在 Shell 中,用括号来表示数组,数组元素用 <space> 符号分割开。

  • 直接定义
1
数组名=(值1 值2 ... 值n)

例如:

1
version=(v1 v2 v3)

或者

1
2
3
version=(v1 
v2
v3)
  • 单独定义各个分量
1
2
3
数组名[0]=值0
数组名[1]=值1
数组名[n]=值n

【注意】可以不使用连续的下标,而且下标的范围没有限制。

2.引用数组元素 

  • 获取数组中某个元素
1
${arrayName[index]}

【注意】 index 从 0 开始

  • 获取数组中所有元素
1
2
${arrayName[@]}
${arrayName[*]}

3.获取数组长度 

3.1使用格式

1
2
3
length=${#arrayName[@]}  # 取得数组元素的个数
length=${#arrayName[*]} # 取得数组元素的个数
lengthn=${#arrayName[n]} # 取得数组单个元素的长度

3.2使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash 
a=(v1.1 v2 v3.3 )

# 取得数组元素的个数
length=${#a[@]}
echo "a's length=${length}"
length=${#a[*]}
echo "a's length=${length}"
# 取得数组单个元素的长度
lengthn=${#a[0]}
echo "a[0]'s length=${lengthn}"
lengthn=${#a[1]}
echo "a[1]'s length=${lengthn}"
lengthn=${#a[n]}
echo "a[n]'s length=${lengthn}"

运行结果如下:

1
2
3
4
5
a's length=3
a's length=3
a[0]'s length=4
a[1]'s length=2
a[n]'s length=4

三、 Shell 替换

如果表达式中包含特殊字符, Shell 将会进行替换。

1.变量替换

变量替换可以根据变量的状态(是否为空、是否定义等)来改变它的值。

${Variable} 变量本来的值
${Variable:-word} 如果变量 Variable 为空或已被删除(unset),那么返回 word,但不改变 Variable 的值。
${Variable:=word} 如果变量 Variable 为空或已被删除(unset),那么返回 word,并将 Variable 的值设置为 word。
${Variable:?message} 如果变量 Variable 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 Variable 是否可以被正常赋值。
若此替换出现在Shell脚本中,那么脚本将停止运行。
${Variable:+word} 如果变量 Variable 被定义,那么返回 word,但不改变 Variable 的值。

2.命令替换

2.1使用格式

命令替换是指 Shell 先执行命令,将输出结果暂时保存,在适当的地方输出。

1
`command`  # 注意是反引号 ` `,按键位于 Esc下方

2.2使用实例

1
2
3
#!/bin/bash
DATE=`date`
echo "Date is $DATE"

运行结果为:

1
Date is 2022年 02月 09日 星期三 18:27:51 CST

四、字典

在shell中是支持字典的,字典就是键值对,就类似这种样子的

1
"key"="value"

字典,在其他语言中也称 map,使用 键-值对(key-value)存储,查找速度快。字典是无序的对象集合,元素是通过键来存取的,而不是通过索引值存取。字典是可变数据类型。

1.字典的定义

声明字典类型要用到关键词 declare :

1
declare -A dict_name

这里一定要-A来声明,-a只能用来声明数组类型。需要注意的是在shell中变量可以不声明直接使用,但是字典不行,字典必须先声明,再使用

2. 字典赋值

我们有两种赋值方式:

1
2
3
4
5
6
7
# 1.定义时直接赋值
declare -A dict
dict=([key1]="value1" [key2]="value2" [key3]="value3")

# 2.动态赋值
declare -A dict
dict['key']=value

注意:

(1)如果键值对已经存在,会覆盖之前的值。

(2)key可以是数字或字符串。

3. 字典相关操作

3.1 打印指定key的value

  • 使用格式
1
echo ${dict_name[key]}
  • 使用实例
1
2
3
4
5
6
7
#!/bin/sh
declare -A dict
dict=([0]="value1" [1]="value2" ["a"]="value3")

echo ${dict[0]}
echo ${dict[1]}
echo ${dict["a"]}

打印结果如下:

1
2
3
4
hk@vm:~/1sharedfiles/6temp/STM32$ ./2.sh 
value1
value2
value3

3.2 打印所有key

  • 使用格式
1
2
echo ${!dict_name[*]}
echo ${!dict_name[@]}
  • 使用实例
1
2
3
4
#!/bin/sh
declare -A dict
dict=([0]="value1" [1]="value2" ["a"]="value3")
echo ${!dict[*]}

打印结果如下:

1
2
hk@vm:~/1sharedfiles/6temp/STM32$ ./2.sh 
a 0 1

3.3 打印所有value

  • 使用格式
1
2
echo ${dict_name[*]}
echo ${dict_name[@]}
  • 使用实例
1
2
3
4
#!/bin/sh
declare -A dict
dict=([0]="value1" [1]="value2" ["a"]="value3")
echo ${dict[*]}

打印结果如下:

1
2
hk@vm:~/1sharedfiles/6temp/STM32$ ./2.sh 
value3 value1 value2

3.4 遍历

  • 使用实例
1
2
3
4
5
6
7
8
9
#!/bin/sh
declare -A dict
dict=([0]="value1" [1]="value2" ["a"]="value3")
echo ${dict[*]}

for key in ${!dict[*]}
do
echo "${key} : ${dict[${key}]}"
done

打印结果如下:

1
2
3
4
5
hk@vm:~/1sharedfiles/6temp/STM32$ ./2.sh 
value3 value1 value2
a : value3
0 : value1
1 : value2

3.5 不声明?

  • 使用实例
1
2
3
4
5
6
7
8
9
#!/bin/sh
#declare -A dict
dict=([0]="value1" [1]="value2" ["a"]="value3")
echo ${!dict[*]}

for key in ${!dict[*]}
do
echo "${key} : ${dict[${key}]}"
done

打印结果如下:

1
2
3
4
hk@vm:~/1sharedfiles/6temp/STM32$ ./2.sh 
0 1
0 : value3
1 : value2

发现我的 “a” 呢?怎么没了,原因就在于没了字典的声明,这样看上去就是一个数组了,数组是不支持字符串下标的,所以就没有a啦,这一点需要注意一下。

我们可以试一下数组支不支持字符下标:

1
2
3
4
5
#!/bin/sh
#declare -A dict
dict=(["a"]="value_a" ["b"]="value_b")
echo ${dict[@]}

打印结果如下:

1
2
hk@vm:~/1sharedfiles/6temp/STM32$ ./2.sh 
value_b

最终的结果就是数组里面只有下标 为 0 的值为 value_b。