一、linux里while [-n “$1”]这里的-n是什么意思?
-n str,字符串不为null,长度大于零
- while [ -n “$1” ] 第一个参数不为空,返回TRUE执行while循环do … done
二、Shell test 命令
Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。
数值测试
参数 | 说明 |
---|---|
-eq | 等于则为真 |
-ne | 不等于则为真 |
-gt | 大于则为真 |
-ge | 大于等于则为真 |
-lt | 小于则为真 |
-le | 小于等于则为真 |
三、shell脚本[] [[]] -n -z 的含义解析
在写脚本的时候,总是搞不懂[] [[]]的区别,这次写一个总结,把它掌握牢固
应用场景分析:
1、在中括号中,判断变量的值, 加不加双引号的问题?
-z 判断 变量的值,是否为空; zero = 0
-
变量的值,为空,返回0,为true
-
变量的值,非空,返回1,为false
-n 判断变量的值,是否为空 name = 名字
-
变量的值,为空,返回1,为false
-
变量的值,非空,返回0,为true
pid=“123”[ -z “$pid” ] 单对中括号变量必须要加双引号
[[ -z $pid ]] 双对括号,变量不用加双引号
[ -n “$pid” ] 单对中括号,变量必须要加双引号
[[ -z $pid ]] 双对中括号,变量不用加双引号
2、多个条件判断,[] 和 [[]] 的区别?
2.1:[[ ]] 双对中括号,是不能使用 -a 或者 -o的参数进行比较的;
&& 并且 || 或 -a 并且 -o 或者
[[ ]] 条件判断 && 并且 || 或
[[ 5 -lt 3 || 3 -gt 6 ]] 一个条件,满足,就成立 或者的关系
[[ 5 -lt 3 || 3 -gt 6 ]] 一个条件满足,就成立 或者的关系
[[ 5 -lt 3 ]] || [[3 -gt 6 ]]
[[ 5 -lt 3 ]] || [[3 -gt 6 ]] 写在外面也可以
&& 必须两个条件同时满足,和上述一样,这里想说明的问题的是:
[[ 5 -lt 3]] -o [[ 3 -gt 6 ]] [[ 5 -lt 3 -o 3 -gt 6 ]]
[[ 5 -lt 3 -a 3 -gt 6 ]] [[ 5 -lt 3 -a 3 -gt 6 ]]
-a 和 -o就不成立了,是因为,[[]] 双对中括号,不能使用 -o和 -a的参数
直接报错:
2.2 [ ] 可以使用 -a -o的参数,但是必须在 [ ] 中括号内,判断条件,例如:
[ 5 -lt 3 -o 3 -gt 2 ] 或者条件成立
[5 -lt 3 ] -o [ 3 -gt 2] 或者条件, 这个不成立,因为必须在中括号内判断
如果想在中括号外判断两个条件,必须用&& 和 || 比较
[5 -lt 3 ] || [ 3 -gt 2]
[5 -gt 3 ] && [ 3 -gt 2] 成立
相对的,|| 和 && 不能在中括号内使用,只能在中括号外使用
3、当判断某个变量的值是否满足正则表达式的时候,必须使用[[ ]] 双对中括号
单对中括号,直接报错:
四、Shell 参数获取的两种方式
方式一
示例
新建一个test.sh文件
#!/bin/bash
echo "shell 名称 = $0"
echo "参数1 = $1"
echo "参数2 = $2"
echo "参数3 = $3"
echo "参数4 = $4"
echo "参数5 = $5"
执行脚本 : sh test.sh 5 6 3 6 2
-
输出的结果:
-
shell 名称 = test.sh 参数1 = 5 参数2 = 6 参数3 = 3 参数4 = 6 参数5 = 2
-
使用该方式有两点需要注意的地方:“$0” 表示的是脚本名称本身
- 优点 : 获取参数方便、简洁
- 缺点 :必须按照指定的顺序输入参数,中间不能漏参数。否则参数将会产生错乱。
方式二 使用 getopts 方式获取
- 如果需要实现类似 job_3.sh -f -d 指定参数值的方式,则推荐使用此种方式。
使用该方式需要使用到Shell内置的函数"getopts"。
在使用之前你需要知道一下几个内置参数的意义:
1.optstring option :字符串,当中为参数选项,会逐个匹配。
2.varname :每次匹配成功的选项
3.arg :参数值
4.$OPTIND :option index,会逐个递增,初始值为1
5.$OPTARG :option argument,不同情况下会有不同的值
该函数的具体使用方式 : getopts [option[:]] VARIABLE
参数说明:
- [option[:]] 表示参数,以"?️b:c:"的方式设置
#####注意细节:
- 以":"开头时:getopts不会提示错误信息,会区分invalid option错误和miss option argument,两种错误的值不一样。invalid option错误时,VARIABLE会被设为*?。为出问题的option。miss option argument时,VARIABLE会被设为*。$OPTARG为出问题的option。
- 不以":"开头时:getopts会提示错误信息。会根据invalid option错误和miss option argument。提示两种不同的错误。VARIABLE都会被设为?。$OPTARG都为空。
- 如果option后面跟了":"表示该option可以接收参数,参数(argument)会被放在$OPTARG中。
示例getopt.sh
#!/bin/bash
while getopts ":a:p:n:s:" opt
do
case $opt in
n)
echo "$OPTARG"
;;
s)
echo "$OPTARG"
;;
a)
echo "$OPTARG"
;;
p)
echo "$OPTARG"
;;
*)
echo "未知参数$opt"
echo "参数值$OPTARG"
;;
?)
echo "未知参数$opt"
echo "参数值$OPTARG"
;;
esac
done
-
执行:sh getopt.sh -a 12 -p 32 -s string -n 你好 -r 世界
-
输出:
12
32
string
你好
install.sh: 非法选项 -- r
未知参数?
参数值
五、Shell脚本之shift用法
shift命令用于对参数的移动(左移),通常用于在不知道传入参数个数的情况下依次遍历每个参数然后进行相应处理(常见于Linux中各种程序的启动脚本)。
示例1:
依次读取输入的参数并打印参数个数:
-
run.sh
-
#!/bin/bash while [ $# != 0 ];do echo "第一个参数为:$1,参数个数为:$#" shift done
-
-
输入如下命令运行:run.sh a b c d e f
-
结果显示如下:
-
第一个参数为:a,参数个数为:6 第一个参数为:b,参数个数为:5 第一个参数为:c,参数个数为:4 第一个参数为:d,参数个数为:3 第一个参数为:e,参数个数为:2 第一个参数为:f,参数个数为:1
-
从上可知 shift(shift 1) 命令每执行一次,
变量的个数($#)减一(之前的$1变量被销毁,之后的$2就变成了$1),而变量值提前一位。
同理,shift n后,前n位参数都会被销毁,比如:
- 输入5个参数: abcd e
那么$1=a,$2=b,$3=c,$4=d,$5=e,执行shift 3操作后,前3个参数a、b、c被销毁,就剩下了2个参数:d,e(这时d=$1,e=$2,其中d由$4—>$1,e由$5—>$2),参考示例如下:
示例2:
#!/bin/bash
echo "参数个数为:$#,其中:"
for i in $(seq 1 $#)
do
eval j=\$$i
echo "第$i个参数($"$i"):$j"
done
shift 3
echo "执行shift 3操作后:"
echo "参数个数为:$#,其中:"
for i in $(seq 1 $#)
do
通过eval把i变量的值($i)作为变量j的名字
eval j=\$$i
echo "第$i个参数($"$i"):$j"
done
输出结果为:
参数个数为:5,其中:
第1个参数($1):a
第2个参数($2):b
第3个参数($3):c
第4个参数($4):d
第5个参数($5):e
执行shift 3操作后:
参数个数为:2,其中:
第1个参数($1):d
第2个参数($2):e
六、eval && exec
都是内建命令。
1.eval
-
bashshell中内建的一个命令,命令后面所跟的内容都认为是参数,但是会两次扫描其参数:第一次扫描会将参数中的变量进行替换;第二次扫描会将后面的参数当作一个shell中的命令组合来执行命令。
-
实际使用中,可以将任意组合的命令赋值给一个变量,然后在需要的位置通过 eval $variable 来执行这个命令。
-
常见用法:
-
直接组合命令 : eval ls -al
-
替换变量
-
可以执行任何值为命令组合的变量
-
变量替换赋值
-
2.exec
- 也是shell内建的一个命令。类似 eval、source,不同的是exec执行后面的命令会替换当前shell进程,而前两者不会。
- 常见用法:
- 用于分离执行脚本,并退出子脚本的shell进程
- 用于设置描述符重定向输入文件内容
- 用于设置描述符重定向输出内容至文件
七、 &&和 ||
- command1 && command2 [&& command3 …]
- 左边的命令返回真后,右边的命令才能够被执行
- 只要有一个命令返回假,后面的命令就不会被执行
- command1 || command2
- 只有左边的命令返回假($? ==1),右边的命令才能被执行,即实现短路逻辑或操作。
- 只要有一个命令返回真,后面的命令就不会被执行
shell脚本getopt使用详解
1.getopt详解
1.1 getopt --help
用法:
getopt optstring parameters
getopt [options] [--] optstring parameters
getopt [options] -o|--options optstring [options] [--] parameters
解析命令选项。
选项:
-a, --alternative 允许长选项以单个 - 开头
-l, --longoptions 要识别的长选项
-n, --name 要报告其错误的程序名称
-o, --options 要识别的短选项名称
-q, --quiet 禁止 getopt(3) 的错误报告
-Q, --quiet-output 无常规输出
-s, --shell 设置 的引用习惯
-T, --test getopt(1) 版本测试
-u, --unquoted 不引用输出
-h, --help display this help
-V, --version display version
可以看到getopt有3种语法用法。
- getopt optstring parameters
- getopt [options] [–] optstring parameters
- getopt [options] -o|–options optstring [options] [–] parameters
其中,optstring可以理解为“解析规则”,parameters可以理解为“待解析的字符串”,getopt的作用就是,依据“解析规则”对“待解析的字符串”进行解析,将散乱、自由的命令行选项和参数进行解析,得到一个完整的、规范化的参数列表,这样再使用while、case和shift进行处理即可。
1.2 命令行选项tips
本章节(1.2 命令行选项tips)选自(有删减):设计shell脚本选项:getopt
在学习getopt如何使用之前,必须先知道命令行的一些常识。这些,都可以通过getopt来实现,但有些实现起来可能会比较复杂。
1.2.1 区分option、parameter、argument、option argument和non-option parameter
parameter和argument都表示参数,前者通常表示独立性的参数,后者通常表示依赖于其它实体的参数。parameter的含义更广,argument可以看作parameter的一种。
例如,定义函数时function foo(x,y){CODE},函数的参数x和y称为parameter。调用函数并传递参数时,foo(arg1,arg2)中的arg1和arg2都是依赖于函数的,称为argument更合适,当然也可以称为更广泛的parameter。
再例如,一个命令行:
tar -zcf a.tar.gz /etc/pki
粗分的话,-z
、-c
、-f
、a.tar.gz
、/etc/pki
都可以称为parameter。细分的话:
- "-z -c -f"称为选项,即option
- a.tar.gz是选项"-f"的选项参数(传递给选项的参数),依赖于选项,称为argument更合适,更严格的称呼是option argument
- /etc/pki既不属于选项,也不属于某个选项的参数,它称为非选项类型的参数,对应的名称为non-option parameter
本文要介绍的是getopt,所以只考虑命令行参数的情况。
1.2.2 短选项和长选项以及它们的"潜规则"
Linux中绝大多数命令都提供了短选项和长选项。一般来说,短选项是只使用一个"-“开头,选项部分只使用一个字符,长选项是使用两个短横线(即”–")开头的。
例如"-a"是短选项,"–append"是长选项。
一般来说,选项的顺序是无所谓的,但并非绝对如此,有时候某些选项必须放在前面,必须放在某些选项的前面、后面。
一般来说,短选项:
可以通过一个短横线"-"将多个短选项连接在一起,但如果连在一起的短选项有参数的话,则必须作为串联的最后一个字符。
例如"-avz"其实会被解析为"-a -v -z",tar -zcf a.tar.gz串联了多个短选项,但"-f"选项有参数a.tar.gz,所以它必须作为串联选项的最后一个字符(实测-f可不在最后,可能是命令做了优化)。
短选项的参数可以和选项名称连在一起,也可以是用空白分隔。例如-n 3和-n3是等价的,数值3都是"-n"选项的参数值。
如果某个短选项的参数是可选的,那么它的参数必须紧跟在选项名后面,不能使用空格分开。
一般来说,长选项:
可以使用等号或空白连接两种方式提供选项参数。例如–file=FILE或–file FILE。
如果某个长选项的参数是可选的,那么它的参数必须使用"="连接。
长选项一般可以缩写,只要不产生歧义即可。
例如,ls命令,以"a"开头的长选项有3个。
$ ls --help | grep -- '--a'
-a, --all do not ignore entries starting with .
-A, --almost-all do not list implied . and ..
--author with -l, print the author of each file
如果想要指定--almost-all
,可以缩写为--alm
;如果想要指定--author
,可以缩写为--au
。如果只缩写为--a
,bash将给出错误提示,长选项出现歧义:
$ ls --a
ls: option '--a' is ambiguous; possibilities: '--all' '--author' '--almost-all'
Try 'ls --help' for more information.
1.2.3 不带参数的选项、可选参数的选项和带参数的选项
有不同类型的命令行选项,这些选项可能不需要参数,也可能参数是可选的,也可能是强制要求参数的。
前面说了,如果某个选项的参数是可选的,那么它的参数必须不能使用空格将参数和选项分开。如果使用空格分隔,则无法判断它的下一个元素是该选项的参数还是非选项类型的参数。
例如,-c和–config选项的参数是可选的,要向这两个选项提供参数,必须写成-cFILE、–config=FILE,如果写成-c FILE、–config FILE,那么命令行将无法判断这个FILE是提供给选项的参数,还是非选项类型的参数。
一般来说,使用可选参数的情况非常少,至少我目前回忆不起来这样的命令(mysql -p选项是一个)。
1.2.4 使用--
将选项(及它们的选项参数)与非选项类型参数进行分隔
unix的命令行中,总是可以在非选项类型的参数之前加上--
,表示选项和选项参数到此为止,后面的都是非选项类型的参数。
例如:
$ cat hello.txt
nihao shijie
this is a line contains hello
this is a other sentence.
hello2
--hello
end, thanks.
$ grep -n hello hello.txt
2:this is a line contains hello
4:hello2
5:--hello
#当想检索hello.txt里的--hello字符串时,--hello会被解析为grep命令的选项而报错
#grep -n "--hello" hello.txt 加上双引号也报错
$ grep -n --hello hello.txt
grep: unrecognized option '--hello'
用法: grep [选项]... PATTERN [FILE]...
试用'grep --help' 来获得更多信息。
#一个可行的方法是,加引号并转义
$ grep -n "\-\-hello" hello.txt
5:--hello
#一个更优雅的方式是使用--将[选项]和 PATTERN [FILE]分隔开
#grep [选项]... PATTERN [FILE]...
$ grep -n -- --hello hello.txt
5:--hello
上述例程就是--
的作用,可以强制禁止一个命令把--
后边的内容识别为选项(或选项参数)。当然,也有例外,并不是所有的命令,在设计的时候都支持了--
的这一功能,如:
#比如想要使用echo回显字符串"-n"到屏幕
#结果发现什么都没显示,是因为-n是echo的一个选项
$ echo "-n"
$
#那加上--,禁止echo把-n识别为选项
#结果显示,连同--都被打印出来了,即echo命令在实现的时候,并没有将--作为分割选项和非选项的标志。
$ echo -- "-n"
-- -n
1.2.5 命令行参数中的短横线开头的并不一定总是短选项,也可能是负数参数
例如seq命令:
seq -w -5 -1 5
其中-5和-1都是负数非选项类型的参数。
1.2.6 模式化(模块化)类型的选项
很多unix命令都将选项进行模块化设计。例如ip命令,address模式、route模式、link模式等等。
ip addr OPTIONS
ip route OPTIONS
ip link OPTIONS
ip neigh OPTIONS
1.2.7 其他特性的选项
有些命令还有比较个性化的选项。
比如head命令,-n NUM
选项,即可以指定为-3
,也可以指定为-n 3
或-n3
。
再比如有的命令支持逻辑运算,例如find命令的-a
、-o
选项。
1.3 用法一
getopt optstring parameters
例:
$ getopt ab:cd -a -b arg_b -d pram1 pram2 pram3
-a -b arg_b -d -- pram1 pram2 pram3
$
主要理解 ab:cd
的意义。这里定义的都是短选项,4个字母代表有4个可以解析出来的选项;b后面的冒号表示这个选项需要一个参数,如果不给选项b一个参数,就会报错:
$ getopt ab:cd -a -d pram1 pram2 pram3 -b
getopt: 选项需要一个参数 -- b
-a -d -- pram1 pram2 pram3
如果上述命令改为下图所示,则不会报错,原因是-b
后的-d
作为了-b
这个选项的参数。这就需要后续再shell脚本的-b
分支中,对齐参数的合法性进行简单地判断。
$ getopt ab:cd -a -b -d pram1 pram2 pram3
-a -b -d -- pram1 pram2 pram3
在1.2.4 使用–将选项(及它们的选项参数)与非选项类型参数进行分隔中,介绍了–分隔选项的机制,也详细讲述了并不是所有的命令(如echo)都支持使用–来分隔选项和非选项。如果我们实现一个脚本(一个命令)的时候,如果使用getopt对参数进行处理,那么我们的脚本是支持–分隔选项和非选项的。
$ getopt ab:cd -ad value1 -- -b best1 value2 value3
-a -d -- value1 -b best1 value2 value3
1.4 用法二 用法三
1.4.1 用法一的短板
用法一和用法二、三在功能上也是有区别的。用法一输出的参数都是不带引号的。而另外两种格式输出的参数都是带引号的。重要的区别不在引号上,而是这种用法不支持处理带空格和引号的参数值。它会将空格当作参数分隔符,而不是根据双引号将二者当作一个参数。
加入有这样一个脚本:
test.sh
#!/bin/sh
while true
do
case "$1" in
-a)
append="$2"
shift 2
;;
--)
shift
break
;;
*)
break
;;
esac
done
for param in "$@"
do
echo ${param}${append}
done
它实现了对输入的参数进行回显的功能,并且当指定-a “string” 时,这个脚本会在将要回显的每个数据后追加上"string"。如:
$ ./test.sh nihao shijie hello world "this is a sentence"
nihao
shijie
hello
world
this is a sentence
$ ./test.sh -a xxxxxxxxxxx nihao shijie hello world "this is a sentence"
nihaoxxxxxxxxxxx
shijiexxxxxxxxxxx
helloxxxxxxxxxxx
worldxxxxxxxxxxx
this is a sentencexxxxxxxxxxx
不过这个脚本有个弊端,它没有参数进行规范化处理(getopt就是用来规范化参数的),导致-a放在命令最后会失去原本想要达到的效果:
$ ./test.sh nihao shijie hello world "this is a sentence" -a xxxxxxxxxxx
nihao
shijie
hello
world
this is a sentence
-a
xxxxxxxxxxx
那我们加上getopt的用法一
test.sh
# 将一下代码加到test.sh的while true的前边
# getopt的用法一
ARGS=$(getopt a: "$@")
#set -- $ARGS的作用是用$ARGS里的内容替换当前shell的$1 $2 ...的内容
set -- $ARGS
此时测试-a放在命令最后的效果,可以看到即使放到了最后,-a同样达到了预期的效果:
$ ./test.sh nihao shijie hello world -a xxxxxxxxxxx
nihaoxxxxxxxxxxx
shijiexxxxxxxxxxx
helloxxxxxxxxxxx
worldxxxxxxxxxxx
但是此处使用的是getopt的用法一,不能正确处理带空格和引号的字符串,我们期望的打印是"this is a sentencexxxxxxxxxxx ",而不是把一句话拆成4个单词:
$ ./test.sh nihao shijie hello world -a xxxxxxxxxxx "this is a sentence"
nihaoxxxxxxxxxxx
shijiexxxxxxxxxxx
helloxxxxxxxxxxx
worldxxxxxxxxxxx
thisxxxxxxxxxxx
isxxxxxxxxxxx
axxxxxxxxxxx
sentencexxxxxxxxxxx
此时就需要用到用法二或用法三
1.4.2 用法二
getopt [options] [--] optstring parameters
将上边的test.sh的代码,改为使用getopt的用法二对参数进行处理。同时增加一条echo语句打印getopt处理完之后的参数。
# getopt的用法二
ARGS=$(getopt -- a: "$@")
echo $ARGS
#set -- $ARGS的作用是用$ARGS里的内容替换当前shell的$1 $2 ...的内容
set -- $ARGS
测试是否可以正常解析带引号和空格的字符串:
$ ./test.sh nihao shijie hello world -a xxxxxxxxxxx "this is a sentence"
-a 'xxxxxxxxxxx' -- 'nihao' 'shijie' 'hello' 'world' 'this is a sentence'
'nihao''xxxxxxxxxxx'
'shijie''xxxxxxxxxxx'
'hello''xxxxxxxxxxx'
'world''xxxxxxxxxxx'
'this'xxxxxxxxxxx'
is'xxxxxxxxxxx'
a'xxxxxxxxxxx'
sentence''xxxxxxxxxxx'
采用用法二之后,发现并没有解决方法一的短板,且多了好多引号,显得更乱了。从新增的echo $ARGS打印来看,方法二解析完的参数,是带引号的,并且正确的使用引号圈出了"this is a sentence"。正是这些引号,导致echo的时候引号增多。这是就需要用到eval命令。在set – $ARGS前加上eval:
# getopt的用法二
ARGS=$(getopt -- a: "$@")
echo $ARGS
#set -- $ARGS的作用是用$ARGS里的内容替换当前shell的$1 $2 ...的内容
eval set -- $ARGS
再次测试:
$ ./test.sh nihao shijie hello world -a xxxxxxxxxxx "this is a sentence"
-a 'xxxxxxxxxxx' -- 'nihao' 'shijie' 'hello' 'world' 'this is a sentence'
nihaoxxxxxxxxxxx
shijiexxxxxxxxxxx
helloxxxxxxxxxxx
worldxxxxxxxxxxx
this is a sentencexxxxxxxxxxx
成功符合预期地执行。
关于eval的介绍,可以参考man手册的eval的example
1.4.3 用法三
getopt [options] -o|–options optstring [options] [–] parameters
用法三和用法二差不多,只是多了一个-o选项,一般指定长选项时(-l选项),会配合使用-o(其他的情况我也不是很了解)。-n选项指定引用的错误程序名。
ARGV=$(getopt -n $(basename $0) -o hd:s: -l help,destination:,source: -- "$@")
eval set -- "$ARGV"
shell 的一些内置命令
常用的一内部命令有:echo、eval、exec、export、read、shift
1、echo命令-在屏幕中输出信息
1)说明
格式:echo args #<== 可以是字符串和变量的组合
说明:将echo命令后面args指定的字符串及变量等显示到标准输出
2、eval命令
1)说明
格式:eval args
功能:当shell程序执行到eval语句时,shell读入参数args,并将他们组合成一个新的命令,然后执行
3、exec命令
1)说明
格式:exec 命令参数
功能:exec命令能够在不创建新的子进程的前提下,转去执行指定的命令,当指定的命令执行完毕后,该经常(最初的shell)就终止了
4、read命令
1)说明
格式:read变量名表
功能:从标准输入读取字符串等信息,传给shell程序内部定义的变量
选项说明:
-a:将分裂后的字段依次存储到指定的数组中,存储的起始位置从数组的index=0开始。
-d:指定读取行的结束符号。默认结束符号为换行符。
-n:限制读取N个字符就自动结束读取,如果没有读满N个字符就按下回车或遇到换行符,则也会结束读取。
-N:严格要求读满N个字符才自动结束读取,即使中途按下了回车或遇到了换行符也不结束。其中换行符或回车算一个字符。
-p:给出提示符。默认不支持"\n"换行,要换行需要特殊处理,见下文示例。例如,"-p 请输入密码:"
-r:禁止反斜线的转义功能。这意味着"\"会变成文本的一部分。
-s:静默模式。输入的内容不会回显在屏幕上。
-t:给出超时时间,在达到超时时间时,read退出并返回错误。也就是说不会读取任何内容,即使已经输入了一部分。
-u:从给定文件描述符(fd=N)中读取数据。
5、shift命令
1)说明
格式:shift-Shift positional parameters
功能:
shift语句会按如下方式重新命名所有的位置参数变量,即$2成为$1、$3成为¥2,在程序中每使用一次shift语句,都会时所有的位置参数一次向左移动一个位置,并使位置参数$#减1,直到减到0为止
6、exit命令
1)说明
格式:exit-Exit the shell
功能:退出shell程序,在exit之后可以有选择
-
参考博客链接:https://blog.csdn.net/keep_lcm/article/details/80551435
-
参考博客链接:https://blog.csdn.net/u013511885/article/details/122545693