https://zhuanlan.zhihu.com/p/651764421
作者:勤奋的小牛
链接:https://zhuanlan.zhihu.com/p/651764421
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
推荐学习网站 https://xukai.work/中文版Bash脚本:https://linuxstory.gitbook.io/advanced-bash-scripting-guide-in-chinese/ shell概述参考文章:https://zhuanlan.zhihu.com/p/452686607 Shell是一种命令解释器,它不仅分离了用户层与操作系统内核,更是一门强大的编程语言。我们称为shell编写的程序为脚本(script)。脚本是一种易于使用的工具,它能够将系统调用、工具软件、实用程序(utility)和已编译的二进制文件联系在一起构建程序。实际上,shell脚本可以调用所有的UNIX命令、实用程序以及工具软件。如果你觉得这还不够,使用像test命令和循环结构这样的shell内建命令能够让脚本更加灵活强大。Shell脚本特别适合完成系统管理任务和那些不需要复杂结构性语言实现的重复工作。在shell中,每个脚本的开头都使用 #! ,就是告知系统文件的执行都需要指定一个解释器。指定一个文件类型的特殊标记。占用 2 字节 。 脚本解释器shell脚本都是以 #! 开头,告知系统该文件的执行需要一个解释器。 常见的解释器类型如下:#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/usr/awk -f解释说明 !/bin/sh linux系统上默认是bash,多数UNIX商业OS中也默认shell。调用脚本执行脚本的三种方式:#方式1
sh helloworld.sh
#方式2
bash helloworld.sh
bash +x helloworld.sh第三种方式有一点特殊./helloworld.sh #需给文件授予执行权限
#实授予权限的方式如下
chmod +x helloworld.sh #授予可执行权限
chmod +rx helloworld.sh #授予任何人可执行可读和可执行权限
chmod u+rx helloworld.sh #只给脚本的所有者可读和可执行权限第一个脚本helloworld#!/bin/bash
echo "helloworld"目前Linux/unix系统中,普遍的shell脚本的第一行是:#!/bin/sh 或者 #!/bin/bash。#!/bin/bash
echo “全部参数:” $@
echo “命令行参数数量:” $#
echo '$0 = ’ $0
echo '$1 = ’ $1
echo '$2 = ’ $2
echo '$3 = ’ $3运行chmod u+rx script.sh #修改权限
./script.sh a b c #执行脚本用户可以输入任意数量的参数,利用for循环,可以读取每一个参数。 #!bin/bash
for循环来读取输入的参数
for i in "KaTeX parse error: Expected 'EOF', got '#' at position 8: @"; #̲@返回一个全部参数的列表,然后使用for循环遍历
do echo $i
done使用“ ”包括起来的命令会被认为是一个命令注释 :Bash 脚本中,以#开头的行就是注释,会被解释器忽略。 # 本行是注释
echo ‘Hello World!’
echo ‘Hello World!’ #是注释多行注释还可以使用以下格式: :<<EOF
注释内容…
注释内容…
注释内容…
EOFEOF也可以使用其他符号: :<<’
注释内容…
注释内容…
注释内容…
’
:<<!
注释内容…
注释内容…
注释内容…
!命令执行结果命令执行结束后,会有一个返回值。0表示执行成功,非0(通常是1)表示执行失败。环境变量
?
可以读取前一个命令的返回值。利用这一点,可以在脚本中对命令执行结果进行判断。
c
d
/
p
a
t
h
/
t
o
/
s
o
m
e
w
h
e
r
e
i
f
[
"
?可以读取前一个命令的返回值。利用这一点,可以在脚本中对命令执行结果进行判断。 cd /path/to/somewhere if [ "
?可以读取前一个命令的返回值。利用这一点,可以在脚本中对命令执行结果进行判断。cd/path/to/somewhereif["?" = 0 ]; then
rm *
else
echo “无法切换目录!” 1>&2
exit 1
ficd /path/to/somewhere这个命令如果执行成功(返回值等于0),就删除该目录里面的文件,否则退出脚本,整个脚本的返回值变为1,表示执行失败。 由于if可以直接判断命令的执行结果,执行相应的操作,上面的脚本可以改写成下面的样子。 if cd /path/to/somewhere; then
rm *
else
echo “Could not change directory! Aborting.” 1>&2
exit 1
fi更简洁的写法是利用两个逻辑运算符&&(且)和 ||(或)。 # 第一步执行成功,才会执行第二步
cd /path/to/somewhere && rm *
第一步执行失败,才会执行第二步
cd /path/to/somewhere || exit 1常见指令echo命令 – 打印到标准输出中echo 通常用于 shell 脚本中,用于显示消息或输出其他命令的结果。$ echo [-neE] [ARGUMENTS]-n 选项,则取消尾随换行符-e 选项,则将解释以下反斜杠转义字符:\ 显示反斜杠字符\a 警报(BEL)\b 显示退格字符\c 禁止任何进一步的输出\e 显示转义字符\f 显示窗体提要字符\n 显示新行\r 显示回车\t 显示水平标签\v 显示垂直标签-E 项禁用转义字符的解释。这是默认值echo “Hello, World!”
Hello, World!若要打印双引号,请将其包含在单引号内,或用反斜杠字符进行转义。$ echo “hello”
hello
$ echo ‘hello “hel”’
hello "hel"模式匹配字符echo 命令可以与模式匹配字符一起使用,比如通配符。 例如,下面的命令将返回所有。 工作目录中的 php 文件。$ echo The PHP files are: *.php
The PHP files are: index.php contact.php functions.php2显示变量echo 还可以显示变量。在下面的示例中,我们将输出当前登录用户的名称:$ echo KaTeX parse error: Expected 'EOF', got '#' at position 9: USER #̲USER 是一个保存用户名的 shell 变量。
admin显示命令的输出使用 $(command)表达式将命令输出包含在 echo 的参数中。 $ echo “The date is: $(date +%D)”
The date is: 04/01/20
$ echo “Today is : $(date +%D)”
Today is : 07/13/23
$ echo “Today is : $(date +%Y%m%d-%H%M%S)”
Today is : 20230713-145600
$ echo "Today is : KaTeX parse error: Expected 'EOF', got '#' at position 132: …n。 :::danger '!#̲/usr/bin/env NA…PATH环境变量里面第一个匹配的NAME。如果不知道某个命令的具体路径,或者希望兼容其他用户的机器,这样的写法就很有用。env命令的参数如下。i, -ignore-environment:不带环境变量启动。u, -unset=NAME:从环境变量中删除一个变量。-help:显示帮助。-version:输出版本信息。 ::: 新建一个不带任何环境变量的 Shell: $ env -i /bin/shexport指令——设置或显示环境变量在shell中执行程序时,shell会提供一组环境变量。export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅及于该次登陆操作。语法export [-fnp][变量名称]=[变量设置值]
-f 代表[变量名称]中为函数名称。
-n 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。
-p 列出所有的shell赋予程序的环境变量。输出系统环境变量export -p 等同于 declare -x添加一个环境变量export AA=2
export -p | grep AA
declare -x AA="2"2. 更改变量的值export aa=5
export -p | grep aa
declare -x aa="5"export USER=LII
echo $USER #LII只在当前终端里有效,当退出后就重新恢复。3. 删除环境变量(无论变量是只存在该shell中还是环境变量)unset aa
export -p | grep aa4. 从文件中添加环境变量cat test.sh
export aaa=1
echo $aaa
source test.sh
1
export -p | grep aaa
declare -x aaa="1"5. 在脚本中,export声明的变量可以为子脚本使用/export.sh
export USER=LIU
/source.sh
source ./export.sh #LIUshift指令 —— 改变脚本参数shift命令可以改变脚本参数,每次执行都会移除脚本当前的第一个参数($1),使得后面的参数向前一位,即$2变成$1、$3变成$2、$4变成$3,以此类推。 while循环结合shift命令,也可以读取每一个参数。 #!/bin/bash
echo “一共输入了 $# 个参数”
while [ “$1” != “” ]; do
echo “剩下 $# 个参数”
echo “参数: $1”
shift #shift指令,每次都会移除第一个参数,从而循环遍历所有输入参数
doneshift命令可以接受一个整数作为参数,指定所要移除的参数个数,默认为1。 shift 3上面的命令移除前三个参数,原来的$4变成$1。注意: 如果shift的参数数量超过了原输入的参数个数,那么就会一直循环输出,而不停止。例如,将上面代码中的shift改成shift 20。重新运行,会发现一直在循环。 shift 20 || break #需加入breakecho “一共输入了 $# 个参数”
while [ “$1” != “” ]; do #可以没有[]
echo “剩下 $# 个参数”
echo “参数: $1”
shift 20 || break #shift指令,每次都会移除第一个参数,从而循环遍历所有输入参数
donegetopts 命令—解析复杂的脚本命令行参数getopts命令用在脚本内部,可以解析复杂的脚本命令行参数,通常与while循环一起使用,取出脚本所有的带有前置连词线(-)的参数。 getopts optstring name:::danger 它带有两个参数。第一个参数optstring是字符串,给出脚本所有的连词线参数。比如,某个脚本可以有三个配置项参数-l、-h、-a,其中只有-a可以带有参数值,而-l和-h是开关参数,那么getopts的第一个参数写成lha:,顺序不重要。注意,a后面有一个冒号,表示该参数带有参数值,getopts规定带有参数值的配置项参数,后面必须带有一个冒号(:)。第二个参数name是一个变量名,用来保存当前取到的配置项参数,即l、h或a。 ::: 例子:#!/bin/bash
while getopts ‘lha:’ OPTION ; do
case “
O
P
T
I
O
N
"
i
n
l
)
e
c
h
o
"
l
i
n
u
x
c
o
n
f
i
g
"
;
;
h
)
e
c
h
o
"
h
s
t
a
n
d
s
f
o
r
h
"
;
;
a
)
a
v
a
l
u
e
=
"
OPTION" in l) echo "linux config" ;; h) echo "h stands for h" ;; a) avalue="
OPTION"inl)echo"linuxconfig";;h)echo"hstandsforh";;a)avalue="OPTARG”
echo “The value provided is $OPTARG”
;;
?)
echo “script usage: $(basename KaTeX parse error: Expected 'EOF', got '&' at position 31: …a somevalue]" >&̲2 exit …(((
O
P
T
I
N
D
−
1
)
)
)
"
上面例子中,
w
h
i
l
e
循环不断执行
g
e
t
o
p
t
s
′
l
h
a
:
′
O
P
T
I
O
N
命令,每次执行就会读取一个连词线参数(以及对应的参数值),然后进入循环体。变量
O
P
T
I
O
N
保存的是,当前处理的那一个连词线参数(即
l
、
h
或
a
)。如果用户输入了没有指定的参数(比如
−
x
),那么
O
P
T
I
O
N
等于
?
。循环体内使用
c
a
s
e
判断,处理这四种不同的情况。如果某个连词线参数带有参数值,比如
−
a
f
o
o
,那么处理
a
参数的时候,环境变量
OPTIND - 1)))"上面例子中,while循环不断执行getopts 'lha:' OPTION命令,每次执行就会读取一个连词线参数(以及对应的参数值),然后进入循环体。变量OPTION保存的是,当前处理的那一个连词线参数(即l、h或a)。如果用户输入了没有指定的参数(比如-x),那么OPTION等于?。循环体内使用case判断,处理这四种不同的情况。如果某个连词线参数带有参数值,比如-a foo,那么处理a参数的时候,环境变量
OPTIND−1)))"上面例子中,while循环不断执行getopts′lha:′OPTION命令,每次执行就会读取一个连词线参数(以及对应的参数值),然后进入循环体。变量OPTION保存的是,当前处理的那一个连词线参数(即l、h或a)。如果用户输入了没有指定的参数(比如−x),那么OPTION等于?。循环体内使用case判断,处理这四种不同的情况。如果某个连词线参数带有参数值,比如−afoo,那么处理a参数的时候,环境变量OPTARG保存的就是参数值。 注意,只要遇到不带连词线的参数,getopts就会执行失败,从而退出while循环。比如,getopts可以解析command -l foo,但不可以解析command foo -l。另外,多个连词线参数写在一起的形式,比如command -lh,getopts也可以正确处理。 变量
O
P
T
I
N
D
在
g
e
t
o
p
t
s
开始执行前是
1
,然后每次执行就会加
1
。等到退出
w
h
i
l
e
循环,就意味着连词线参数全部处理完毕。这时,
OPTIND在getopts开始执行前是1,然后每次执行就会加1。等到退出while循环,就意味着连词线参数全部处理完毕。这时,
OPTIND在getopts开始执行前是1,然后每次执行就会加1。等到退出while循环,就意味着连词线参数全部处理完毕。这时,OPTIND - 1就是已经处理的连词线参数个数,使用shift命令将这些参数移除,保证后面的代码可以用$1、
2
等处理命令的主参数。个连词线参数带有参数值,比如
−
a
f
o
o
,那么处理
a
参数的时候,环境变量
2等处理命令的主参数。个连词线参数带有参数值,比如-a foo,那么处理a参数的时候,环境变量
2等处理命令的主参数。个连词线参数带有参数值,比如−afoo,那么处理a参数的时候,环境变量OPTARG保存的就是参数值。配置项参数终止符 --/–和–开头的参数,会被 Bash 当作配置项解释。但是,有时它们不是配置项,而是实体参数的一部分,比如文件名叫做f或–file。 $ cat -f
$ cat --file上面命令的原意是输出文件-f和–file的内容,但是会被 Bash 当作配置项解释。 这时就可以使用配置项参数终止符–,告诉 Bash,在它后面的参数开头的-和–不是配置项,只能当作实体参数解释。 $ cat – -f
$ cat – --file 如果要确保某个变量不会被当作配置项解释,就要在它前面放上参数终止符–。 $ ls – KaTeX parse error: Expected 'EOF', got '#' at position 10: myPath #̲--强制变量myPath只能当作实体参数(即路径名)解释如果变量不是路径名,就会报错: $ myPath=”-l"
$ ls – $myPath
ls: 无法访问’-l’: 没有那个文件或目录如果想在文件里面搜索–hello,这时也要使用参数终止符–。 $ grep --hello example.txt //报错,没有–hello
$ grep – hello example.txt //寻找hellodeclare 命令 — 声明一些特殊类型的变量declare命令可以声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量: declare OPTION VARIABLE=valuedeclare命令的主要参数(OPTION)如下:-a:声明数组变量。-f:输出所有函数定义。-F:输出所有函数名。-i:声明整数变量。-l:声明变量为小写字母。-p:查看变量信息。-r:声明只读变量。-u:声明变量为大写字母。-x:该变量输出为环境变量。declare命令如果用在函数中,声明的变量只在函数内部有效,等同于local命令。不带任何参数时,declare命令输出当前环境的所有变量,包括函数在内,等同于不带有任何参数的set命令。 -i 参数声明整数变量以后,可以直接进行数学运算 【注意:赋值的时候直接不能有空格】$ declare -i val1=12 val2=5
$ declare -i result
$ result=val1val2
$ echo $result
60如果变量result不声明为整数,val1val2会被当作字面量,不会进行整数运算。另外,val1和val2其实不需要声明为整数,因为只要result声明为整数,它的赋值就会自动解释为整数运算。 注意,一个变量声明为整数以后,依然可以被改写为字符串。 $ declare -i var=12
$ var=foo
$ echo
v
a
r
0
上面例子中,变量
v
a
r
声明为整数,覆盖以后,
B
a
s
h
不会报错,但会赋以不确定的值,可能输出
0
,也可能输出的是
3
。
−
x
参数等同于
e
x
p
o
r
t
命令,可以输出一个变量为子
S
h
e
l
l
的环境变量。
var 0上面例子中,变量var声明为整数,覆盖以后,Bash 不会报错,但会赋以不确定的值,可能输出0,也可能输出的是3。 -x参数等同于export命令,可以输出一个变量为子 Shell 的环境变量。
var0上面例子中,变量var声明为整数,覆盖以后,Bash不会报错,但会赋以不确定的值,可能输出0,也可能输出的是3。−x参数等同于export命令,可以输出一个变量为子Shell的环境变量。 declare -x foo
等同于
$ export foo-r参数可以声明只读变量,无法改变变量值,也不能unset变量。 echo “-r 声明的变量是只读变量,不能修改----”
declare -r con=1
con=2
echo $? #看上一个指令的值
unset con
echo $?
./declare.sh: 行 2: con:只读变量
1
./declare.sh: 第 6 行: unset: con:无法取消设定: 只读 variable
1上面例子中,后两个赋值语句都会报错,命令执行失败。 -u参数声明变量为大写字母,可以自动把变量值转成大写字母。 $ declare -u foo
$ foo=upper
$ echo $foo
UPPER-l参数声明变量为小写字母,可以自动把变量值转成小写字母。 $ declare -l bar
$ bar=LOWER
$ echo $bar
lower-p参数输出变量信息。 $ foo=hello
$ declare -p foo
declare – foo=“hello”
$ declare -p bar
bar:未找到上面例子中,declare -p可以输出已定义变量的值,对于未定义的变量,会提示找不到。如果不提供变量名,declare -p输出所有变量的信息。$ declare -p-f参数输出当前环境的所有函数,包括它的定义。 $ declare -f-F参数输出当前环境的所有函数名,不包含函数定义。 $ declare -F
#!/bin/bash
function hello()
{
echo “hello”
}
function alice() {
echo “alice: $@”
echo “$0: $1 $2 $3
4
"
e
c
h
o
"
4" echo "
4"echo"# arguments” #2个参数
}
echo “--------------------f------------------------------”
declare -f #显示函数的定义
echo “--------------------F------------------------------”
declare -F #仅会显示函数名readonly 命令 — 声明只读变量readonly命令等同于declare -r,用来声明只读变量,不能改变变量值,也不能unset变量。 $ readonly foo=1
$ foo=2
$ echo $?
./declare.sh: 行 2: dd:只读变量
1readonly命令有三个参数。-f:声明的变量为函数名。-p:打印出所有的只读变量。-a:声明的变量为数组。let 命令—可以直接执行算术表达式let命令声明变量时,可以直接执行算术表达式: $ let foo=1+2
$ echo $foo #3let命令的参数表达式如果包含空格,就需要使用引号: $ let "foo = 1 + 2"let可以同时对多个变量赋值,赋值表达式之间使用空格分隔: $ let “v1 = 1” “v2 = v1++” #let声明变量v1和v2,其中v2等于v1++,表示先返回v1的值,然后v1自增
$ echo
v
1
,
v1,
v1,v2 #2,1
#!/bin/bash
未进行shellcheck检查
a=5
let a+=2
echo "plus : $a " # a = 7 和
let a-=2
echo “minus : $a” # a = 5 差
let a*=2
echo “multi : $a” # a = 10 积
let a/=2
echo “div : $a” # a = 5 商
let a%=2
echo “Modulo : $a” # a = 1 余数expr 命令—支持算术运算支持算术运算expr命令支持算术运算,可以不使用((…))语法: $ expr 3 + 2 #5expr命令支持变量替换: $ foo=3
$ expr $foo + 2 #5expr命令也不支持非整数参数: $ expr 3.5 + 2
expr: 非整数参数字符串操作expr length – 求字符串长度$ echo expr length $str
expr index – 求字符串索引$ expr index "
s
t
r
i
n
g
"
′
string" '
string"′substring’expr match – 匹配字符串expr substr – 截取子串具体将在字符串操作篇细讲 exit 命令 — 终止当前脚本的执行exit命令用于终止当前脚本的执行,并向 Shell 返回一个退出值。 exit上面命令中止当前脚本,将最后一条命令的退出状态,作为整个脚本的退出状态。 exit命令后面可以跟参数,该参数就是退出状态。 # 退出值为0(成功)
$ exit 0
退出值为1(失败)
$ exit 1退出时,脚本会返回一个退出值。 :::danger 脚本的退出值,0表示正常,1表示发生错误,2表示用法不对,126表示不是可执行脚本,127表示命令没有发现。如果脚本被信号N终止,则退出值为128 + N。 简单来说,只要退出值非0,就认为执行出错。source 命令 — 执行一个脚本source命令用于执行一个脚本,通常用于重新加载一个配置文件。 $ source .bashrcsource命令最大的特点是在当前 Shell 执行脚本,不像直接执行脚本时,会新建一个子Shell。所以,source命令执行脚本时,不需要export变量。 # 当前 Shell 新建一个变量 foo
$ foo=1
打印输出 1
$ source test.sh
1
打印输出空字符串
$ bash test.sh上面例子中,当前 Shell 的变量foo并没有export,所以直接执行无法读取,但是source执行可以读取。 source命令的另一个用途,是在脚本内部加载外部库。 #!/bin/bash
$ source ./lib.sh
function_from_lib上面脚本在内部使用source命令加载了一个外部库,然后就可以在脚本里面,使用这个外部库定义的函数。source有一个简写形式,可以使用一个点(.)来表示。$ . .bashrc变量变量表示数据的方法。是计算机为了保存数据项而在内存中分配的一个位置或一组位置的标识或名字。变量名就是保存值的地方。shell变量有系统变量和自定义变量两种。对于变量名的声明规则类似于其他编程语言。由字母、数字、下划线组成,但不能以数字开头。hello_123 # 合法
123_hello # 不合法内部变量(系统变量)主要记录实际工作中使用的,也不要记住。用到了再查即可。可以通过env指令显示所有的环境变量$ env 或者
$ printenv系统变量说明备注
B
A
S
H
V
E
R
S
I
O
N
查看
b
a
s
h
的版本
BASH_VERSION查看bash的版本
BASHVERSION查看bash的版本BASHbash的二进制程序文件的路径
U
S
E
R
当前用户一般是
r
o
o
t
USER当前用户一般是root
USER当前用户一般是rootEUID有效用户ID
E
U
I
D
不一定与
EUID 不一定与
EUID不一定与UID相同
F
U
N
C
T
I
O
N
在函数中,可直接打印当前函数的名字
FUNCTION在函数中,可直接打印当前函数的名字
FUNCTION在函数中,可直接打印当前函数的名字GROUP当前用户所属的组一个组ID列表
H
O
S
T
N
A
M
E
当前主机名字
HOSTNAME当前主机名字
HOSTNAME当前主机名字HOSTTYPE当前主机类型$PATH可执行文件的搜索路径以冒号分隔的目录列表PWD当前工作目录BASHPIDBash 进程的进程 IDSHELLshell名字DISPLAY图形环境的显示器名字,通常是:0,表示 X Server 的第一个显示器。EDITOR默认的文本编辑器HOME用户的主目录HOST当前主机的名称 很多环境变量很少发生变化,而且是只读的,可以视为常量。由于它们的变量名全部都是大写,所以传统上,如果用户要自己定义一个常量,也会使用全部大写的变量名。注:Bash变量名区分大小写,HOME和home是两个不同的变量。查看单个环境变量的值,可以使用printenv命令或echo命令。 $ printenv PATH #printenv命令后面的变量名,不用加前缀$
或者
$ echo P A T H / u s r / l o c a l / s b i n : / u s r / l o c a l / b i n : / u s r / s b i n : / u s r / b i n : / r o o t / b i n 自定义变量用户创建变量的时候,变量名必须遵守下面的规则:字母、数字和下划线字符组成第一个字符必须是一个字母或一个下划线,不能是数字不允许出现空格和标点符号定义变量:变量名 = 变量值,等号两侧不能有空格(与其它语言可设空格的区别之处)。变量名一般习惯使用大写。设置变量: s e t 变量名 = 变量值。删除变量: u n s e t 变量名 = 变量值。声明静态变量: r e a d o n l y 变量名,静态变量不能用 u n s e t 使用变量: PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin自定义变量用户创建变量的时候,变量名必须遵守下面的规则:字母、数字和下划线字符组成第一个字符必须是一个字母或一个下划线,不能是数字不允许出现空格和标点符号定义变量:变量名=变量值,等号两侧不能有空格(与其它语言可设空格的区别之处)。变量名一般习惯使用大写。设置变量:set 变量名=变量值。删除变量:unset 变量名=变量值。声明静态变量:readonly 变量名,静态变量不能用unset使用变量: PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin自定义变量用户创建变量的时候,变量名必须遵守下面的规则:字母、数字和下划线字符组成第一个字符必须是一个字母或一个下划线,不能是数字不允许出现空格和标点符号定义变量:变量名=变量值,等号两侧不能有空格(与其它语言可设空格的区别之处)。变量名一般习惯使用大写。设置变量:set变量名=变量值。删除变量:unset变量名=变量值。声明静态变量:readonly变量名,静态变量不能用unset使用变量:变量名变量赋值简单赋值:a=123 && echo $a命令行赋值给变量#使用反引号 str=cat helloworld.sh #直接使用 ( . . ) 格式 o s = (..)格式 os= (..)格式os=(cat /etc/os-release)局部变量:只在代码块或函数中可见可用。外部不可用。Bash变量:不区分类型,都是字符串,不允许进行数值计算,除非变量中包含数字。如果变量的值中间有空格,则使用引号(单引号或双引号均可)扩起来。 输出变量会影响用户接口和shell的行为。环境变量是一个全局变量。 用户创建的变量仅可用于当前Shell,子 Shell 默认读取不到父 Shell 定义的变量。为了把变量传递给子Shell,需要使用export命令。这样输出的变量,对于子Shell来说就是环境变量。 通过 export 命令将变量声明为环境变量即可。export 变量名=变量值
方式 1 :直接export导入,命令行窗口重启后失效
export LD_LIBRARY_PATH=/usr/local/cuda/lib
方式 2
加入到 root目录下的 .bashrc 中
使用 source ./bashrc 使修改后的配置信息生效,命令行窗口重启或者机器重启均不会失效
查看环境变量是否生效
echo $变量名
echo $LD_LIBRARY_PATH对于环境变量的查看# 方式 1 :查看所有变量(包括环境变量和自定义变量)
set
方式 2 :只能查看环境变量
env引用变量引用:将字符串使用双引号""扩起来。作用:保护字符串的特殊字符(通配符)不被shell重新解释或者扩展。[root@centos8 data]# var01=3
[root@centos8 data]# echo
v
a
r
013
在引用变量时,需要使用
var01 3在引用变量时,需要使用
var013在引用变量时,需要使用来进行引用变量的值。 变量在使用过程中,如果没有
作为前缀,需要思考如下情况:被声明或被赋值。是否被
u
n
s
e
t
被使用
e
x
p
o
r
t
方式导入是否作为信号量。赋值的两种方式:使用
=
方式。直接使用
r
e
a
d
命令。
b
a
s
h
作为前缀,需要思考如下情况:被声明或被赋值。是否被unset被使用export 方式导入是否作为信号量。赋值的两种方式:使用 = 方式。直接使用read命令。bash
作为前缀,需要思考如下情况:被声明或被赋值。是否被unset被使用export方式导入是否作为信号量。赋值的两种方式:使用=方式。直接使用read命令。bash ls -l [Vv]*
-rw-rw-r-- 1 bozo bozo 324 Apr 2 15:05 VIEWDATA.BAT
-rw-rw-r-- 1 bozo bozo 507 May 4 14:25 vartrace.sh
-rw-rw-r-- 1 bozo bozo 539 Apr 14 17:11 viewdata.sh
bash$ ls -l ‘[Vv]’
ls: [Vv]: No such file or directory 可以看到,提示不存在该文件。这里的’[Vv]*被当成了文件名。 在日常沟通和写作中,当我们引用一个短语的时候,我们会将它单独隔开并赋予它特殊的意义,而在bash脚本中,当我们_引用_一个字符串,意味着保留它的_字面量_。使用双引号可以防止字符串被分割。即使参数中拥有很多空白分隔符,被包在双引号中后依旧是算作单一字符。当字符分割或者保留空白符出现问题时,才需要在echo语句中用双引号包住参数。List=“one two three”
for a in KaTeX parse error: Expected 'EOF', got '#' at position 10: List #̲ 空白符将变量分成几个部分。 …a"
done
echo “—”
for a in “KaTeX parse error: Expected 'EOF', got '#' at position 9: List" #̲ 在单一变量中保留所有空格。 …a”
done
one two three单引号(’ ‘)与双引号类似,但是在单引号中不能引用变量,因为 $ 不再具有特殊含义。在单引号中,除’之外的所有特殊字符都将会被直接按照字面意思解释。可以认为单引号(“全引用”)是双引号(“部分引用”)的一种更严格的形式。 删除变量unset命令用来删除一个变量: unset NAME这个命令不是很有用。因为不存在的Bash变量一律等于空字符串,所以即使unset命令删除了变量,还是可以读取这个变量,值为空字符串。所以,删除一个变量,也可以将这个变量设成空字符串。$ foo=’’
$ foo= #由于不存在的值默认为空字符串,所以可以在等号右边不写任何值特殊字符变量shell中参数:
0
、
0、
0、?、
!
、
!、
!、
、
、
、、KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲、@字符符号功能(用途)$0shell文件本身的文件名
1
~
1~
1~n添加到Shell的各参数值。$1是第1参数、
2
是第
2
参数、
…
、
2是第2参数、…、
2是第2参数、…、n表示第n个参数。⚠️注意:10以上要用大括号,如{10}。
?
最后运行命令的结束代码(返回值),执行成功返回
0
,不成功则返回非零值(一般解释为错误码)。
?最后运行命令的结束代码(返回值),执行成功返回0,不成功则返回非零值(一般解释为错误码)。
?最后运行命令的结束代码(返回值),执行成功返回0,不成功则返回非零值(一般解释为错误码)。
S
h
e
l
l
本身的
P
I
D
(
P
r
o
c
e
s
s
I
D
)
Shell本身的PID(ProcessID)
Shell本身的PID(ProcessID)!Shell最后运行的后台Process的PID
−
使用
S
e
t
命令设定的
F
l
a
g
一览
-使用Set命令设定的Flag一览
−使用Set命令设定的Flag一览所有参数列表。如 "$“用「”」括起来的情况、以"$1 $2 …
n
"
的形式输出所有参数。
n" 的形式输出所有参数。
n"的形式输出所有参数。@所有参数列表。如 “$@“用「”」括起来的情况、以”$1" “
2
"
…
"
2" … "
2"…"n” 的形式输出所有参数。KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲添加到Shell的参数个数;;…PWD~-先前的工作目录。等同与 KaTeX parse error: Expected group after '^' at position 7: OLDPWD^̲^大写字母,,小写字母 和 $# 的区别*:不被双引号包含时,两者没有区别。当被双引号包含时, $ 是将所有参数看作一整个数据。而 KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲ 则是将每个参数看作一个数据。? : 上一个命令的退出码,用来判断上一个命令是否执行成功。返回值是0,表示上一个命令执行成功;如果不是零,表示上一个命令执行失败。 $ ls doesnotexist #ls命令查看一个不存在的文件,导致报错
ls: doesnotexist: No such file or directory
$ echo
?
1
? 1
?1$ :当前 Shell 的进程 ID echo
这个特殊变量可以用来命名临时文件。
L
O
G
F
I
L
E
=
/
t
m
p
/
o
u
t
p
u
t
l
o
g
.
这个特殊变量可以用来命名临时文件。 LOGFILE=/tmp/output_log.
这个特殊变量可以用来命名临时文件。LOGFILE=/tmp/outputlog.$_:上一个命令的最后一个参数 $ grep dictionary /usr/share/dict/words
dictionary
$ echo
/
u
s
r
/
s
h
a
r
e
/
d
i
c
t
/
w
o
r
d
s
_ /usr/share/dict/words
/usr/share/dict/words!:最近一个后台执行的异步命令的进程 ID $ firefox &
[1] 11064
$ echo ! 11064 f i r e f o x 是后台运行的命令, ! 11064firefox是后台运行的命令, !11064firefox是后台运行的命令,!返回该命令的进程 ID。 $0:当前 Shell 的名称(在命令行直接执行时)或者脚本名(在脚本中执行时)。 $ echo 0 b a s h 0 bash 0bash-:当前 Shell 的启动参数。 $ echo − h i m B H s i m a g e . p n g - himBHsimage.png −himBHsimage.png@和KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲#表示脚本的参数数量,$@表示脚本的参数值。具体实例脚本#!/bin/bash
echo “This is $0 ---- : $0”
echo “This is $1 ---- : $1”
echo “This is $2 ---- : $2”
echo “This is $? ---- : $?”
echo “This is $$ ---- : $$”
echo “This is $! ---- : $!”
echo “This is $_ ---- : $_”
echo “This is $- ---- : $-”
echo “This is $* ---- : $*”
echo “This is $@ ---- : $@”
echo "This is $# ---- : $#"直接执行且打印信息如下:bash +x paramshell.sh 7 77
#打印信息
This is $0 ---- : paramshell.sh # 显示文件名
This is $1 ---- : 7 # 第1个参数
This is $2 ---- : 77 # 第2个参数
This is $? ---- : 0 # 执行完毕后的返回值 0 — True,1 — False
This is $$ ---- : 1549879 # 该程序执行时的本身进程ID
This is $! ---- : # shell后台运行的进程ID
This is $_ ---- : This is $! ---- : # 变量保存之前执行命令的最后一个参数的值
This is $* ---- : 7 77 # 参数值列表
This is $@ ---- : 7 77 # 参数值列表
This is $# ---- : 2 # 参数的个数image.png参数替换参数替换用来处理或扩展变量。 ${parameter}等同于 $parameter,是变量 parameter 的值。在一些特定的环境下,只允许使用不易混淆的
p
a
r
a
m
e
t
e
r
形式。可以用于连接变量与字符串
y
o
u
r
i
d
=
{parameter} 形式。可以用于连接变量与字符串your_id=
parameter形式。可以用于连接变量与字符串yourid={USER}-on-
H
O
S
T
N
A
M
E
e
c
h
o
"
{HOSTNAME} echo "
HOSTNAMEecho"your_id"
echo "Old $PATH =
P
A
T
H
"
P
A
T
H
=
PATH" PATH=
PATH"PATH={PATH}:/opt/bin # 在脚本执行过程中临时在 $PATH 中加入 /opt/bin。
echo "New $PATH =
P
A
T
H
"
PATH"
PATH"{parameter-default}, ${parameter:-default}在没有设置变量的情况下使用缺省值。var1=1
var2=2
没有设置 var3。
echo KaTeX parse error: Expected '}', got 'EOF' at end of input: {var1-var2} # 1
echo KaTeX parse error: Expected '}', got 'EOF' at end of input: {var3-var2} # 2
^ 注意前面的 $ 前缀。
echo ${username-whoami
}
如果变量 u s e r n a m e 没有被设置,输出 ‘ w h o a m i ‘ 的结果。 username 没有被设置,输出 `whoami` 的结果。 username没有被设置,输出‘whoami‘的结果。{parameter-default} 与 p a r a m e t e r : − d e f a u l t 的作用几乎相同,唯一不同的情况就是当变量 p a r a m e t e r 已经被声明但值为空时。当变量已经声明了,此时使用 {parameter:-default} 的作用几乎相同,唯一不同的情况就是当变量 parameter 已经被声明但值为空时。当变量已经声明了,此时使用 parameter:−default的作用几乎相同,唯一不同的情况就是当变量parameter已经被声明但值为空时。当变量已经声明了,此时使用{parameter-default} 将不会使用缺省值;而使用${parameter:-default}会使用缺省值 #!/bin/bash
param-sub.sh
无论变量的值是否为空,其是否已被声明决定了缺省设置的触发。
username0=
echo “username0 has been declared, but is set to null.”
echo “username0 = ${username0-whoami
}”
将不会输出 whoami
的结果。
echo
echo username1 has not been declared.
echo “username1 = ${username1-whoami
}”
将会输出 whoami
的结果。
username2=
echo “username2 has been declared, but is set to null.”
echo “username2 = ${username2:-whoami
}”
^
因为这里是 :- 而不是 -,所以将会输出 whoami
的结果。
与上面的 username0 比较。
variable=
变量已被声明,但其值为空。
echo “KaTeX parse error: Expected 'EOF', got '#' at position 18: …aribale-0}" #̲ 没有输出。 echo "{variable:-1}” # 1当传入的命令行参数的数量不足时,可以使用这种缺省参数结构。DEFAULT_FILENAME=generic.data
filename=KaTeX parse error: Expected '}', got 'EOF' at end of input: {1:-DEFAULT_FILENAME}
如果没有其他特殊情况,下面的代码块将会操作文件 “generic.data”。
代码块开始
…
…
…
代码块结束${parameter=default}, ${parameter:=default}在没有设置变量的情况下,将其设置为缺省值。两种形式的作用几乎相同,唯一不同的情况与上面类似,就是当变量 parameter 已经被声明但值为空时。echo ${var=abc} # abc
echo ${var=xyz} # abc
v a r 已经在第一条语句中被赋值为 a b c ,因此第二条语句将不会改变它的值。 var 已经在第一条语句中被赋值为 abc,因此第二条语句将不会改变它的值。 var已经在第一条语句中被赋值为abc,因此第二条语句将不会改变它的值。{parameter+alt_value}, ${parameter:+alt_value}如果变量已被设置,使用 alt_value,否则使用空值。两种形式的作用几乎相同,唯一不同的情况就是当变量 parameter 已经被声明但值为空时,看下面的例子。echo “###### ${parameter+alt_value} ########”
echo
a=${param1+xyz}
echo “a = $a” # a =
param2=
a=${param2+xyz}
echo “a = $a” # a = xyz
param3=123
a=${param3+xyz}
echo “a = $a” # a = xyz
echo
echo “###### ${parameter:+alt_value} ########”
echo
a=${param4:+xyz}
echo “a = $a” # a =
param5=
a=${param5:+xyz}
echo “a = $a” # a =
不同于 a=${param5+xyz}
param6=123
a=${param6:+xyz}
echo "a = KaTeX parse error: Expected 'EOF', got '#' at position 9: a" #̲ a = xyz{parameter?err_msg}, ${parameter:?err_msg}如果变量已被设置,那么使用原值,否则输出 err_msg 并且终止脚本,返回 错误码 1。两种形式的作用几乎相同,唯一不同的情况与上面类似,就是当变量 parameter 已经被声明但值为空时。样例 10-7. 如何使用变量替换和错误信息#!/bin/bash
检查系统环境变量。
这是一种良好的预防性维护措施。
如果控制台用户的名称 $USER 没有被设置,那么主机将不能够识别用户。
: ${HOSTNAME?} ${USER?} ${HOME?} ${MAIL?}
echo
echo “Name of the machine is $HOSTNAME.”
echo “You are $USER.”
echo “Your home directory is $HOME.”
echo “Your mail INBOX is located in $MAIL.”
echo
echo “If you are reading this message,”
echo “critcial environmental variables have been set.”
echo
echo样例 10-8. 参数替换与 “usage” 消息#!/bin/bash
usage-message.sh
: ${1?“Usage: $0 ARGUMENT”}
如果命令行参数缺失,脚本将会在这里结束,并且返回下面的错误信息。
usage-message.sh: 1: Usage: usage-message.sh ARGUMENT
echo “These two lines echo only if command-line parameter given.”
echo "command-line parameter = “$1"”
exit 0 # 仅当命令行参数存在是才会从这里退出。
在传入和未传入命令行参数的情况下查看退出状态。
如果传入了命令行参数,那么 “$?” 的结果是0。
如果没有,那么 " ? " 的结果是 1 。 i m a g e . p n g 参数替换用来处理或扩展变量。运算符赋值运算符符 = :通用赋值操作符,可用于算术和字符串的赋值。 ?" 的结果是1。image.png参数替换用来处理或扩展变量。 运算符赋值运算符符=: 通用赋值操作符,可用于算术和字符串的赋值。 ?"的结果是1。image.png参数替换用来处理或扩展变量。运算符赋值运算符符=:通用赋值操作符,可用于算术和字符串的赋值。 echo $((a=1)) #1
$ echo KaTeX parse error: Expected 'EOF', got '#' at position 9: a #̲1逗号,在((…))内部是求值运算符,执行前后两个表达式,并返回后一个表达式的值。 $ echo $((foo = 1 + 2, 3 * 4)) #12
$ echo KaTeX parse error: Expected 'EOF', got '#' at position 6: foo #̲3算术运算符运算符描述备注+加…((…)),使其变成算术表达式,返回算术运算的值。::😒 echo KaTeX parse error: Expected 'EOF', got '#' at position 12: ((2 + 2)) #̲4:::danger((…))内部可以用圆括号改变运算顺序。 $((…))结构可以嵌套。 这个语法只能计算整数,否则会报错:::# 报错
$ echo
(
(
1.5
+
1
)
)
b
a
s
h
:
语法错误
:
:
:
d
a
n
g
e
r
如果在
((1.5 + 1)) bash: 语法错误:::danger如果在
((1.5+1))bash:语法错误:::danger如果在((…))里面使用字符串,Bash 会认为那是一个变量名。如果不存在同名变量,Bash 就会将其作为空值,因此不会报错。::😒 echo $(( “hello” + 2))
2
$ echo KaTeX parse error: Expected 'EOF', got '#' at position 131: …umber:十六进制数base#̲number:base进制的数… echo $((0xff)) #255
$ echo $((2#11111111)) #255
$ echo $((16 << 2)) #64
$ echo $((16 >> 2)) #4
$ echo $((17 & 2)) #0
$ echo $((17 | 2)) #19
$ echo $((7 ^ 2)) #5 异或运算
echo “0number 8进制-----”
$ echo $((022)) #18 = 28+2
$ echo $((0x22)) #34 = 216+2 16进制
$ echo $((3 > 2)) #1
$ echo $(( (3 > 2) || (4 <= 1) )) #1
$ expr 3 + 2 #5
a=5
$ expr $a + 3 #8
$ let a+=2
$ echo “a = $a " #a=7比较运算符运算符英文全称示例-eqequal[ 1 -eq 1 ]为 true-nenot equal[ 1 -ne 1 ]为 false-gtgreater than[ 2 -gt 1 ]为 true-ltlesser than[ 2 -lt 1 ]为 false-gegreater or equal[ 2 -ge 1 ]为 tbnrue-lelesser or equal[ 2 -le 1 ]为 false逻辑运算符运算符描述等价使用具体说明&&逻辑与(AND)-a需两个为True||逻辑或(OR)-o其中之一为True:::danger KaTeX parse error: Expected 'EOF', got '&' at position 53: …于或相等==:相等!=:不相等&̲&:逻辑与||:逻辑或!:逻辑… echo $((3 > 2)) #1
$ echo $(( (3 > 2) || (4 <= 1) )) #1三元运算符执行一个单独的逻辑测试。它用起来类似于if/then/else语句。 $ a=0
$ echo $((a<1 ? 1 : 0)) #1
$ echo $((a>1 ? 1 : 0)) #0第一个表达式为真时,返回第二个表达式的值,否则返回第三个表达式的值。位运算:::danger KaTeX parse error: Expected 'EOF', got '&' at position 75: …数字的所有位向右移动指定的位。&̲:位的“与”运算,对两个数字的… echo $((16 << 2)) #64
$ echo $((16 >> 2)) #4
$ echo $((17 & 2)) #0
$ echo $((17 | 2)) #19
$ echo KaTeX parse error: Expected 'EOF', got '#' at position 19: … ^ 2)) #̲5 异或运算运算符优…v1” -gt “
v
2
"
−
o
"
v2" -o "
v2"−o"v1” -lt “
v
2
"
−
a
−
e
"
v2" -a -e "
v2"−a−e"filename” ]
这样写不清晰…
if [[ “ v 1 " − g t " v1" -gt " v1"−gt"v2” ]] || [[ “ v 1 " − l t " v1" -lt " v1"−lt"v2” ]] && [[ -e “$filename” ]]
好多了 – 把逻辑判断分散到多个组之中算术拓展算术扩展为脚本中的(整数)算术操作提供了强有力的工具。你可以使用反引号、双圆括号或者 let 将字符串转换为数学表达式。 使用 反引号 的算术扩展(通常与 expr 一起使用)z=expr $z + 3
# ‘expr’ 命令执行了算术扩展。
a=expr 5 + 3
echo “5 + 3 = $a”
a=expr $a + 1
echo
echo “a + 1 = $a”
echo “(incrementing a variable)”
a=expr 5 % 3
modulo
echo
echo "5 mod 3 = $a"使用 双圆括号 或 let 的算术扩展。事实上,在算术扩展中,反引号已经被双圆括号 ((…)) 和
(
(
.
.
.
)
)
以及
l
e
t
所取代。
z
=
((...)) 以及 let 所取代。z=
((...))以及let所取代。z=((
z
+
3
)
)
z
=
z+3)) z=
z+3))z=((z+3)) # 同样正确。
# 在双圆括号内,参数引用形式可用可不用。
$((EXPRESSION)) 是算术扩展。 # 不要与命令替换混淆。
双圆括号不是只能用作赋值算术结果。
n=0
echo “n = $n” # n = 0
(( n += 1 )) # 自增。
(( $n += 1 )) 是错误用法!
echo "n = KaTeX parse error: Expected 'EOF', got '#' at position 19: … #̲ n = 1 let z=z+…)、反斜杠(\)等。 双引号如果其中使用了变量,则变量内容也会被替换。如果再次使用引号,则使用转义符。双引号比单引号宽松,大部分特殊字符在双引号里面,都会失去特殊含义,变成普通字符。 三个特殊字符除外:美元符号(KaTeX parse error: Undefined control sequence: \) at position 14: )、反引号(`)和反斜杠(\̲)̲。这三个字符在双引号之中,依然… echo “$SHELL”
/bin/bash
$ echo “date
”
Mon Jan 27 13:33:18 CST 2020双引号还有一个作用,就是保存原始命令的输出格式。 # 单行输出
$ echo KaTeX parse error: Expected 'EOF', got '#' at position 8: (cal) #̲如果(cal)不放在双引号之中,echo就会将所有结果以单行输出
一月 2020 日 一 二 三 四 五 六 1 2 3 … 31
原始格式输出
$ echo “$(cal)”
一月 2020
日 一 二 三 四 五 六
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31image.png转义符转义符是一种引用单个字符的方法。添加转义符(\) 使得shell中的某个字符失去原有特殊含义。 :::danger 注意:在echo 和 sed 中谨慎使用转义符,否则会有相反效果。 ::: | 转义符 | 含义 | | — | — | | \n | 表示新的一行 | | \r | 表示回车 | | \c | 不换行 | | \t | 表示水平制表符 | | \v | 表示垂直制表符 | | \b | 表示后退符 | | \a | 表示"alert" (蜂鸣器或闪烁) | | \0xx | 转换为八进制的ASCII码,等价于0xx | | " | 表示双引号 | | $ | 表示$本身的意思,跟在后面的变量名不会起作用 | | \ | 表示反斜线 |赋值给变量的字符串的元素也会被转义, 但是不能把一个单独的转义符赋值给变量。如果想要在命令行使用这些不可打印的字符,可以把它们放在引号里面,然后使用echo命令的-e参数。 $ echo a\tb
atb
$ echo -e “a\tb”
a b
$ echo -e “a\nb”
a
b字符串的长度直接使用 ${#string} 来计算字符串的长度# 3 个语法格式
${#string} # 方式 1
expr length KaTeX parse error: Expected 'EOF', got '#' at position 12: string #̲ 方式 2 expr "string" : ‘.*’ # 方式 3
具体实例
str=“hello world”
echo “After using #str : ${#str}”
echo “Use expr length :
(
e
x
p
r
l
e
n
g
t
h
"
(expr length "
(exprlength"str”)" # 该方式不建议使用,因为shellcheck也建议使用第一种方式
echo “Use expr :
(
e
x
p
r
"
(expr "
(expr"str” : ‘.')"大括号{}是必需的,否则Bash会将$#理解成脚本的参数个数,将变量名理解成文本。 mystr=“hello string”
echo “mystr length is: KaTeX parse error: Expected '}', got '#' at position 2: {#̲mystr}" #计算字符…mystr” : '.’ #12 echo
expr length “$mystr”` #12
str=adafamfa
echo expr length $str
#8expr match – 起始部分字符串匹配长度expr match "
s
t
r
i
n
g
"
′
string" '
string"′substring’
expr “
s
t
r
i
n
g
"
:
′
string" : '
string":′substring’其中,
s
u
b
s
t
r
i
n
g
是一个正则表达式。
e
c
h
o
"
起始匹配字符串长度
−
−
−
−
m
a
t
c
h
"
s
t
r
i
n
g
=
a
b
c
A
B
C
D
123243
e
c
h
o
‘
e
x
p
r
m
a
t
c
h
"
substring 是一个正则表达式。echo "起始匹配字符串长度----match" string=abcABCD123243 echo `expr match "
substring是一个正则表达式。echo"起始匹配字符串长度−−−−match"string=abcABCD123243echo‘exprmatch"string” ‘abc[A-Z]*.2’ #9 echo
expr “KaTeX parse error: Expected 'EOF', got '#' at position 36: ….2'` #̲9expr index -- …string” ‘$substring’
详细实例 ---- 未进行shellcheck检查
str=“SolerHO123456”
echo
(
e
x
p
r
i
n
d
e
x
"
(expr index "
(exprindex"str" H) # 结果为 6
echo | awk ‘{ print ("’“KaTeX parse error: Expected 'EOF', got '}' at position 14: (str)"'","H")}̲' #6返回字符串中出现…{string:position}在string中从位置position开始提取子串${string:position:length}在string中从位置position开始提取 length长度的字串在 $string 中截取自 $position 起,长度为
l
e
n
g
t
h
的字符串。
:
:
:
d
a
n
g
e
r
length 的字符串。 :::danger
length的字符串。:::danger{string:position:length} expr substr $string $position
l
e
n
g
t
h
e
x
p
r
m
a
t
c
h
"
lengthexpr match "
lengthexprmatch"string” ‘$substring’ # 在 $string 中截取自 $position 起的字符串,其中
s
u
b
s
t
r
i
n
g
是正则表达式。
e
x
p
r
"
substring 是正则表达式。expr "
substring是正则表达式。expr"string" : ‘$substring’ #在 $string 中截取自 $position 起的字符串,其中
s
u
b
s
t
r
i
n
g
是正则表达式
e
x
p
r
m
a
t
c
h
"
substring 是正则表达式 expr match "
substring是正则表达式exprmatch"string" ‘.$substring’ #从 $string 结尾部分截取 $substring 字符串,其中
s
u
b
s
t
r
i
n
g
是正则表达式。
e
x
p
r
"
substring 是正则表达式。expr "
substring是正则表达式。expr"string" : '.KaTeX parse error: Expected '}', got 'EOF' at end of input: …k '{ print ("'"(string)"’",
p
o
s
i
t
i
o
n
,
position,
position,length)}’ #awk的索引是从1开始:::1. ${string:position:length} #索引是从0开始
2. $ expr substr $string $position $length #索引是从1开始
3. $ expr match "KaTeX parse error: Can't use function '\(' in math mode at position 10: string" '\̲(̲substring)’
4. $ expr "KaTeX parse error: Can't use function '\(' in math mode at position 12: string" : '\̲(̲substring)’
5. $ expr match “KaTeX parse error: Can't use function '\(' in math mode at position 12: string" '.*\̲(̲substring)’
6. $ expr “KaTeX parse error: Can't use function '\(' in math mode at position 14: string" : '.*\̲(̲substring)’
7. $ echo | awk '{ print (”'”
(
s
t
r
i
n
g
)
"
′
"
,
(string)"'",
(string)"′",position,$length)}’
$ count=frogfootman
$ echo ${count:4:4} #foot这种语法不能直接操作字符串,只能通过变量来读取字符串,并且不会改变原始字符串。 $ echo ${“hello”:2:3} # "hello"不是变量名,Bash 报错如果省略length,则从位置offset开始,一直返回到字符串的结尾。 $ count=frogfootman
$ echo KaTeX parse error: Expected 'EOF', got '#' at position 12: {count:4} #̲footman如果offset…{variable:-word}的变量的设置默认值语法混淆。这时还可以指定length,length可以是正值,也可以是负值(负值不能超过offset的长度)。:::danger
p
a
r
a
m
e
t
e
r
:
−
d
e
f
a
u
l
t
将会得到整个字符串。括号或者增加空格都可以
"
转义
"
位置参数。
:
:
:
{parameter:-default} 将会得到整个字符串。括号或者增加空格都可以"转义"位置参数。 :::
parameter:−default将会得到整个字符串。括号或者增加空格都可以"转义"位置参数。::: foo=“This string is long.”
$ echo ${foo:-5} #This string is long.
$ echo ${foo: -5} #long.
$ echo ${foo: -5:2} #lo
$ echo ${foo: -5:-2} #lon完整实例#!/bin/bash
echo “字符串分片 >>>>>>>>”
substr=hellosubstr
echo “expr sustr----------”
echo expr substr $substr 2 5
#ellos,索引是从1开始
echo expr substr $substr -2 5
#空
echo “expr match----------”
stringZ=abcABC123ABCabc
echo expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'
#abcABC1
echo expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'
#abcABC1
echo expr match "$stringZ" '.*\([A-C][A-C][A-C][a-c]*\)'
#ABCabc 从 $string 结尾部分截取
s
u
b
s
t
r
i
n
g
字符串
e
c
h
o
‘
e
x
p
r
"
substring 字符串 echo `expr "
substring字符串echo‘expr"stringZ" : ‘.([A-C][A-C][A-C][a-c])’` #ABCabc
echo “----------------------”
echo ${substr:2:5} #llosu 截取子字符串,2开始位置,5是截取的个数
echo ${substr:2} #llosubstr 省略长度,则一直到字符串的结尾
从右向左截取
echo ${substr:-5} #hellosubstr 输出全部
echo ${substr: -5} #ubstr 必须要有一个空格,从末尾开始往前数5个字符
echo ${substr: -5:2} #ub 从倒数第五作为开始,取2个长度
echo ${*:2} # 输出第二个及之后的所有位置参数
echo ${@:2} # 同上案例:产生一个随机的8个字符的字符串#!/bin/bash
产生一个8个字符的随机字符串。
if [ -n “$1” ]
then
str0=“
1
"
e
l
s
e
s
t
r
0
=
"
1" else str0="
1"elsestr0="$”
fi
POS=2
LEN=8
str1=
(
e
c
h
o
"
(echo "
(echo"str0" | md5sum | md5sum) #将字符串通过管道计算两次 md5 来进行两次混淆
randstring=“KaTeX parse error: Expected '}', got 'EOF' at end of input: {str1:POS:$LEN}”
echo “$randstring” #a71224ce
exit KaTeX parse error: Expected 'EOF', got '#' at position 112: …头部的替换与删除删除头部字符串#̲ 如果 pattern 匹配变…{variable#pattern}## 如果 pattern 匹配变量 variable 的开头,删除最长匹配(贪婪匹配)的部分,返回剩余部分${variable##pattern}匹配模式pattern可以使用*、?、[]等通配符: $ myPath=/home/cam/book/long.file.name
$ echo ${myPath#//} #匹配的模式是//,*可以匹配任意数量的字符
cam/book/long.file.name #删除最短匹配(非贪婪)的字符串,也就是/home/
$ echo ${myPath##//} ##删除最长匹配(贪婪)的字符串,也就是/home…/book/
long.file.name下面写法可以删除文件路径的目录部分,只留下文件名: echo “删除匹配字符串--------”
path=/home/cam/book/long.file.name
echo ${path#//} #删除最短匹配字符串,cam/book/long.file.name
echo ${path##/*/} #删除最长匹配字符串,long.file.name
stringZ=abcABC123ABCabc
|----| 最长
|----------| 最短
echo ${stringZ#a*C} # 123ABCabc
删除 ‘a’ 与 ‘c’ 之间最短的匹配。
echo ${stringZ##a*C} # abc
删除 ‘a’ 与 ‘c’ 之间最长的匹配。匹配替换头部字符如果要将头部匹配的部分,替换成其他内容,采用下面的写法: 替换 $string 中最前端匹配到的 $substring 为 $replacement。# 模式必须出现在字符串的开头
${string/#string/replacement}
示例
$ foo=JPG.JPG
$ echo ${foo/#JPG/jpg} #只能替换第一次匹配到的字符
jpg.JPG字符串尾部的匹配与删除删除尾部字符以下两种语法可以检查字符串结尾,是否匹配给定的模式。如果匹配成功,就删除匹配的部分,返回剩下的部分。原始变量不会发生变化。 # 如果 pattern 匹配变量 variable 的结尾,删除最短匹配(非贪婪匹配)的部分,返回剩余部分
${variable%pattern}
如果 pattern 匹配变量 variable 的结尾,删除最长匹配(贪婪匹配)的部分,返回剩余部分
${variable%%pattern}
$ path=/home/cam/book/long.file.name
$ echo ${path%.*} #/home/cam/book/long.file
$ echo ${path%%.*} #/home/cam/book/long
stringZ=abcABC123ABCabc
|| 最短
|------------| 最长
echo ${stringZ%b*c} # abcABC123ABCa
从结尾处删除 ‘b’ 与 ‘c’ 之间最短的匹配。
echo ${stringZ%%b*c} # a
从结尾处删除 ‘b’ 与 ‘c’ 之间最长的匹配。下面写法可以删除路径的文件名部分,只留下目录部分: $ path=/home/cam/book/long.file.name
$ echo KaTeX parse error: Expected '}', got 'EOF' at end of input: …am/book匹配替换尾部字符{string/%substring/replacement}替换 $string 中最末端匹配到的 $substring 为 $replacement。stringZ=abcABC123ABCabc
echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc
# 将前端的 ‘abc’ 替换为 ‘XYZ’
echo KaTeX parse error: Expected '}', got '#' at position 81: … #̲ 将末端的 'abc' 替换为…substring 和 $replacement 可以是文本字符串也可以是变量。案例:将当前目录下所有.TXT转换成.txt# 将当前目录下所有后缀名为 “TXT” 的文件改为 “txt” 后缀。
例如 “file1.TXT” 改为 “file1.txt”。
SUFF=TXT
suff=txt
for i in
(
l
s
∗
.
(ls *.
(ls∗.SUFF)
do
mv -f $i
(
i
(i%.
(iSUFF).$suff
除了从变量 $i 右侧匹配到的最短的字符串之外,
#+ 其他一切都保持不变。
done任意位置的字符串模式匹配替换# 如果 pattern 匹配变量 variable 的一部分,最长匹配(贪婪匹配)的那部分被 string 替换,但仅替换第一个匹配
${variable/pattern/string} #替换匹配到的第一个 $substring 为 $replacement。
如果 pattern 匹配变量 variable 的一部分,最长匹配(贪婪匹配)的那部分被 string 替换,所有匹配都替换
${variable//pattern/string}
$ path=/home/cam/foo/foo.name
$ echo ${path/foo/bar} #/home/cam/bar/foo.name
$ echo ${path//foo/bar} #/home/cam/bar/bar.name案例2stringZ=abcABC123ABCabc
echo ${stringZ/abc/xyz} # 将第一个匹配到的abc替换成xyz xyzABC123ABCabc
echo ${stringZ//abc/xyz} # 将所有匹配到的abc替换成xyz xyzABC123ABCxyz
echo ---------------
echo “$stringZ” # abcABC123ABCabc
echo ---------------
# 字符串本身并不会被修改!
echo “----参数替换------”
srcstr=abc
replace=000
echo KaTeX parse error: Expected '}', got 'EOF' at end of input: {stringZ/srcstr/$replace} #000ABC123ABCabc
echo KaTeX parse error: Expected '}', got 'EOF' at end of input: {stringZ//srcstr/$replace} #000ABC123ABC000
如果没有给定 $replacement 字符串会怎样?
echo ${stringZ/abc} #ABC123ABCabc 直接就是删除匹配到的字符
echo ${stringZ//abc} #ABC123ABC下面的例子将分隔符从:换成换行符。 $ echo -e ${PATH//😕‘\n’}
/usr/local/bin
/usr/bin
/bin
…image.png上面例子中,echo命令的-e参数,表示将替换后的字符串的\n字符,解释为换行符。 ${!varprefix*}, ${!varprefix@}匹配先前声明过所有以 varprefix 作为变量名前缀的变量。# 这是带 * 或 @ 的间接引用的一种变换形式。
xyz23=whatever
xyz23=
a=${!xyz*} # 扩展为声明变量中以 “xyz”
^ ^ ^ + 开头变量名。
echo "a = KaTeX parse error: Expected 'EOF', got '#' at position 9: a" #̲ a = xyz23 xyz…{!xyz@} # 同上。
echo “a = $a” # a = xyz23 xyz24
echo “—”
abc23=something_else
b=${!abc*}
echo "b = KaTeX parse error: Expected 'EOF', got '#' at position 9: b" #̲ b = abc23 c={!b} # 这是我们熟悉的间接引用的形式。
echo $c # something_else间接引用 样例 10-5. 模拟 getopt#!/bin/bash
getopt_simple()
{
echo “getopt_simple()”
echo “Parameter is “$@””
until [ -z "$1" ]
do
echo "Processing parame is '$1'"
if [ ${1:0:1} = '/' ]
then
tmp=${1:1}
parameter=${tmp%%=*}
value=${tmp##=*}
echo "Parameter: '$parameter', value: '$value'"
eval $parameter=$value
fi
shift
done
}
将所有参数传递给 getopt_simple()。
getopt_simple $*
echo “test is ‘ t e s t ′ " e c h o " t e s t 2 i s ′ test'" echo "test2 is ' test′"echo"test2is′test2’”
exit 0 # 可以查看该脚本的修改版 UseGetOpt.sh。image.png改变大小写改变变量的大小写: ${varname^^} #转为大写
${varname,} #转为小写
$ foo=heLLo
$ echo ${foo^^} #HELLO
$ echo ${foo,} #helloimage.png使用 awk 处理字符串在 Bash 脚本中可以调用字符串处理工具 awk 来替换内置的字符串处理操作。样例 10-6. 使用另一种方式来截取和定位子字符串#!/bin/bash
String=23skidoo1
注意不同字符串索引系统:
Bash 中第一个字符的位置为0。
Awk 中第一个字符的位置为1。
echo KaTeX parse error: Expected 'EOF', got '#' at position 51: … #̲ skid echo `exp…String" 3 4` # skid
Awk 中与 ${string:pos:length} 等价的是 substr(string,pos,length)。
echo | awk ’
{ print substr (“'”${String}“'”,3,4)
}’ # skid
将空的 “echo” 通过管道传递给 awk 作为一个模拟输入,
#+ 这样就不需要提供一个文件名来操作 awk 了。
echo expr index "$String" i
#5
echo | awk ‘{ print index ("’“${String}”‘",“i”)}’ #5
exit 0数组bash支持只支持一维数组。数组元素可使用符号 var[number] 来初始化。脚本使用 declare -a var 语句来指定一个数组。数组访问:通过下标的方式访问 —
v
a
r
[
n
u
m
b
e
r
]
。数组元素的下标由
0
开始,和
C
语言类似。下标可以是整数或算术表达式,其值应大于或等于
0
。在数组中,
{var[number]}。数组元素的下标由0开始,和C语言类似。下标可以是整数或算术表达式,其值应大于或等于0。在数组中,
var[number]。数组元素的下标由0开始,和C语言类似。下标可以是整数或算术表达式,其值应大于或等于0。在数组中,{#array[*]} 和 ${#array[@]} 表示数组中元素的个数。shell中使用括号来表示数组,元素之间则使用 空格符号 分隔。# 等号两边不能空格
array_name=(element_1 element_2 … element_N)
声明奇数
arr_odd=(1 3 5 7 9)可以按照默认顺序赋值,也可以在每个值前面指定位置。 $ array=(a b c)
$ array=([2]=c [0]=a [1]=b)
$ days=(Sun Mon Tue Wed Thu Fri Sat)
$ days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)
数组名[索引] 方式
$ (array_name[index])
示例
echo
a
r
r
o
d
d
[
2
]
打印所有元素
{arr_odd[2]}打印所有元素
arrodd[2]打印所有元素{arr_odd[*]} # 建议使用该方式
${arr_odd[@]} # 该方式在shellcheck中会无法认可,对在shell中,使用unset命令来删除数组元素。语法格式:# 删除特定的元素
unset array_name[index]
删除整个数组
unset array_name # 使用数组名,不写下标先用declare -a命令声明一个数组,也是可以的: $ declare -a ARRAYNAMEread -a命令则是将用户的命令行输入,存入一个数组: $ read -a dice #将用户的命令行输入,存入数组dice读取数组$ echo ${array[i]} # 是索引
$ array[0]=a
$ echo ${array[0]}
a
$ echo
a
r
r
a
y
[
0
]
a
[
0
]
如果不加大括号,
B
a
s
h
会直接读取
array[0] a[0]如果不加大括号,Bash 会直接读取
array[0]a[0]如果不加大括号,Bash会直接读取array首成员的值,然后将[0]按照原样输出。 读取所有成员$ foo=(a b c d e f)
$ echo ${foo[*]}
a b c d e f
for i in "${names[]}"; do
echo
i
d
o
n
e
i
m
a
g
e
.
p
n
g
i doneimage.png
idoneimage.png{activities[]}放在双引号之中,所有成员就会变成单个字符串返回。 $ for act in “${activities[*]}”;
do
echo “Activity: $act”;
done
Activity: swimming water skiing canoeing white-water rafting surfing拷贝一个数组的最方便方法,就是写成下面这样: $ hobbies=( “${activities[*]}” ) #数组activities被拷贝给了另一个数组hobbies
$ hobbies=( “${activities[@]}” diving ) #新数组hobbies在数组activities的所有成员之后,又添加了一个成员数组的长度要想知道数组的长度(即一共包含多少成员),可以使用下面两种语法。 ${#array[]}
${#array[@]}
$ a[100]=foo
$ echo ${#a[]}
1
$ echo ${#a[@]}
1
#把字符串赋值给100位置的数组元素,这时的数组只有一个元素注意,如果用这种语法去读取具体的数组成员,会返回该成员的字符串长度: $ a[100]=foo
$ echo KaTeX parse error: Expected '}', got '#' at position 2: {#̲a[100]} #{#a[100]}实际上是返回数组第100号成员a[100]的值(foo)的字符串长度
3提取数组序号
!
a
r
r
a
y
[
@
]
或
{!array[@]}或
!array[@]或{!array[]},可以返回数组的成员序号,即哪些位置是有值的: $ arr=([5]=a [9]=b [23]=c)
$ echo ${!arr[@]}
5 9 23
$ echo ${!arr[]}
5 9 23利用这个语法,也可以通过for循环遍历数组。 arr=(a b c d)
for i in ${!arr[@]};do
echo
a
r
r
[
i
]
d
o
n
e
提取数组成员
{arr[i]} done提取数组成员
arr[i]done提取数组成员{array[@]:position:length}的语法可以提取数组成员: $ food=( apples bananas cucumbers dates eggs fajitas grapes )
$ echo KaTeX parse error: Expected 'EOF', got '#' at position 16: {food[@]:1:1} #̲{food[@]:1:1}返回从数组1号位置开始的1个成员
bananas
$ echo KaTeX parse error: Expected 'EOF', got '#' at position 16: {food[@]:1:3} #̲{food[@]:1:3}返回从1号位置开始的3个成员
bananas cucumbers dates如果省略长度参数length,则返回从指定位置开始的所有成员: $ echo KaTeX parse error: Expected 'EOF', got '#' at position 15: {food[@]:4} #̲返回从4号位置开始到结束的所有… foo=(a b c)
$ echo ${foo[@]}
a b c
$ foo+=(d e f)
$ echo ${foo[@]}
a b c d e f删除数组删除一个数组成员,使用unset命令: $ foo=(a b c d e f)
$ echo ${foo[@]}
a b c d e f
$ unset foo[2] #删除了数组中的第三个元素
$ echo ${foo[@]}
a b d e f直接将数组变量赋值为空字符串,相当于“隐藏”数组的第一个成员: $ foo=(a b c d e f)
$ foo=‘’
$ echo ${foo[@]}
b c d e funset ArrayName可以清空整个数组: $ unset ARRAY
$ echo ${ARRAY[*]}
<–no output–>关联数组关联数组使用字符串而不是整数作为数组索引。 declare -A可以声明关联数组:declare -A colors
colors[“red”]=“#ff0000”
colors[“green”]=“#00ff00”
colors[“blue”]="#0000ff"关联数组必须用带有-A选项的declare命令声明创建。相比之下,整数索引的数组,可以直接使用变量名创建数组,关联数组就不行。 访问关联数组成员的方式,几乎与整数索引数组相同。 echo ${colors[“blue”]}分支与循环控制双中括号 [[ … ]] 结构在bash中,引入 [[ … ]] 扩展测试命令。这种方式一般直接使用数学符号判断时使用。例如if [[ “$1” > "
2
"
]
]
;
t
h
e
n
双圆括号
(
(
.
.
.
)
)
结构允许进行算术扩展和赋值。例如:
a
=
2" ]];then双圆括号((...)) 结构允许进行算术扩展和赋值。例如:a=
2"]];then双圆括号((...))结构允许进行算术扩展和赋值。例如:a=(( 5 + 3 ))。该风格类似于C语言风格中的变量操作处理方式。 if和if嵌套if/then结构用来判断命令列表的退出状态码是否为0。if commands; then
commands
[elif commands; then
commands…]
[else
commands]
fi这个命令分成三个部分:if、elif和else,后两个部分是可选的。if单分支语法格式:if [ condition ];then
command1
command2
…
fi # 注意不能少了fi结尾
#例如
if [ “$1” -gt 18 ];then
echo “you are an adult”
fiif多分支语法格式:if [ condition ];then
command1
command2
…
else
command3
fi
#例如
if [ "KaTeX parse error: Expected 'EOF', got '#' at position 299: …件都不成立时要执行的部分。 #̲判断条件是环境变量USER是否等于foo,如果等于就输出Hello foo.,否则输出其他内容
if test $USER = “foo”; then
echo “Hello foo.”
else
echo “You are not foo.”
fiif和then写在同一行时,需要分号分隔。分号是 Bash 的命令分隔符。它们也可以写成两行,这时不需要分号。 if true
then
echo ‘hello world’
fi
if false
then
echo ‘it is false’ # 本行不会执行
fi除了多行的写法,if结构也可以写成单行。 $ if true; then echo ‘hello world’; fi
hello world
$ if false; then echo “It’s true.”; fi注意,if关键字后面也可以是一条命令,该条命令执行成功(返回值0),就意味着判断条件成立。 $ if echo ‘hi’; then echo ‘hello world’; fi
hi
hello worldif后面可以跟任意数量的命令。这时,所有命令都会执行,但是判断真伪只看最后一个命令,即使前面所有命令都失败,只要最后一个命令返回0,就会执行then的部分。 $ if false; true; then echo ‘hello world’; fi
hello worldif后面有两条命令(false;true;),第二条命令(true)决定了then的部分是否会执行。 image.pngimage.pngtest指令if结构的判断条件,一般使用test命令,有三种形式: # 写法一
test expression
写法二
[ expression ]
写法三
[[ expression ]]
#三种形式等价,但是第三种形式还支持正则判断,前两种不支持expression是一个表达式。这个表达式为真,test命令执行成功(返回值为0);表达式为伪,test命令执行失败(返回值为1)。注意,第二种和第三种写法,[和]与内部的表达式之间必须有空格。 $ test -f /etc/hosts
$ echo $?
0
$ [ -f /etc/hosts ]
$ echo $?
0下面把test命令的三种形式,用在if结构中,判断一个文件是否存在。 # 写法一
if test -e /tmp/foo.txt ; then
echo “Found foo.txt”
fi
写法二
if [ -e /tmp/foo.txt ] ; then
echo “Found foo.txt”
fi
写法三
if [[ -e /tmp/foo.txt ]] ; then
echo “Found foo.txt”
fi判断表达式if关键字后面,跟的是一个命令。这个命令可以是test命令,也可以是其他命令。命令的返回值为0表示判断成立,否则表示不成立。因为这些命令主要是为了得到返回值,所以可以视为表达式。 a == b a与b是否相等 (a、b是数值)
a -ge b a 是否大于等于 b
a -gt b a 是否大于 b
a -le b a 是否小于等于 b
a -lt b a 是否小于 b
a -ne b a 是否不等于 b
< > >= <= 【需要在(())里使用】 注意在 [] 结构里 < 需要被转义。
str1 = str2 str1是否与str2相同(str1、str2是字符串)
str1 != str2 str1是否与str2不同
str1 < str2 str1是否小于str2
str1 > str2 str1是否大于str2
-n str 判断str长度是否非零
-z str str长度是否为0
-d file 判断file是否为目录
-e file 判断file是否存在
-f file 检查file是否为文件
-r file 判断文件是否可读
-s file 判断file是否存在并非空
-w file 判断file是可写
-x file 判断file是可执行文件判断:::danger[ -a file ]:如果 file 存在,则为true。[ -b file ]:如果 file 存在并且是一个块(设备)文件,则为true。[ -c file ]:如果 file 存在并且是一个字符(设备)文件,则为true。[ -d file ]:如果 file 存在并且是一个目录,则为true。[ -e file ]:如果 file 存在,则为true。[ -f file ]:如果 file 存在并且是一个普通文件,则为true。[ -g file ]:如果 file 存在并且设置了组 ID,则为true。[ -G file ]:如果 file 存在并且属于有效的组 ID,则为true。[ -h file ]:如果 file 存在并且是符号链接,则为true。[ -k file ]:如果 file 存在并且设置了它的“sticky bit”,则为true。[ -L file ]:如果 file 存在并且是一个符号链接,则为true。[ -N file ]:如果 file 存在并且自上次读取后已被修改,则为true。[ -O file ]:如果 file 存在并且属于有效的用户 ID,则为true。[ -p file ]:如果 file 存在并且是一个命名管道,则为true。[ -r file ]:如果 file 存在并且可读(当前用户有可读权限),则为true。[ -s file ]:如果 file 存在且其长度大于零,则为true。[ -S file ]:如果 file 存在且是一个网络 socket,则为true。[ -t fd ]:如果 fd 是一个文件描述符,并且重定向到终端,则为true。 这可以用来判断是否重定向了标准输入/输出/错误。[ -u file ]:如果 file 存在并且设置了 setuid 位,则为true。[ -w file ]:如果 file 存在并且可写(当前用户拥有可写权限),则为true。[ -x file ]:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true。[ FILE1 -nt FILE2 ]:如果 FILE1 比 FILE2 的更新时间更近,或者 FILE1 存在而 FILE2 不存在,则为true。[ FILE1 -ot FILE2 ]:如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为true。[ FILE1 -ef FILE2 ]:如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为true。 :::#!/bin/bash
FILE=~/.bashrc
if [ -e “
F
I
L
E
"
]
;
t
h
e
n
i
f
[
−
f
"
FILE" ]; then if [ -f "
FILE"];thenif[−f"FILE” ]; then
echo “
F
I
L
E
i
s
a
r
e
g
u
l
a
r
f
i
l
e
.
"
f
i
i
f
[
−
d
"
FILE is a regular file." fi if [ -d "
FILEisaregularfile."fiif[−d"FILE” ]; then
echo “
F
I
L
E
i
s
a
d
i
r
e
c
t
o
r
y
.
"
f
i
i
f
[
−
r
"
FILE is a directory." fi if [ -r "
FILEisadirectory."fiif[−r"FILE” ]; then
echo “
F
I
L
E
i
s
r
e
a
d
a
b
l
e
.
"
f
i
i
f
[
−
w
"
FILE is readable." fi if [ -w "
FILEisreadable."fiif[−w"FILE” ]; then
echo “
F
I
L
E
i
s
w
r
i
t
a
b
l
e
.
"
f
i
i
f
[
−
x
"
FILE is writable." fi if [ -x "
FILEiswritable."fiif[−x"FILE” ]; then
echo “
F
I
L
E
i
s
e
x
e
c
u
t
a
b
l
e
/
s
e
a
r
c
h
a
b
l
e
.
"
f
i
e
l
s
e
e
c
h
o
"
FILE is executable/searchable." fi else echo "
FILEisexecutable/searchable."fielseecho"FILE does not exist”
exit 1
fi
#-z 字符串为空,即字符串长度为0。
String=‘’ # 长度为0的字符串变量。
if [ -z “$String” ]
then
echo “$String is null.”
else
echo “$String is NOT null.”
fi # $String is null.
#-n:字符串非空(null)。
if [ -n $string1 ] # 并未声明或是初始化 string1。
then
echo “String “string1” is not null.”
else
echo “String “string1” is null.”
fi
尽管没有初始化 string1,但是结果显示其非空。
echo
if [ $string1 ] # 这次只有一个 $string1。
then
echo “String “string1” is not null.”
else
echo “String “string1” is null.”
fi # 结果正确。
独立的 [ … ] 测试运算符可以用来检测字符串是否为空。
但是最好将字符串进行引用(if [ " s t r i n g 1 " ] )。 string1" ])。 string1"])。FILE要放在双引号之中,这样可以防止变量 F I L E 为空,从而出错。因为 FILE为空,从而出错。因为 FILE为空,从而出错。因为FILE如果为空,这时[ -e F I L E ] 就变成 [ − e ] ,这会被判断为真。而 FILE ]就变成[ -e ],这会被判断为真。而 FILE]就变成[−e],这会被判断为真。而FILE放在双引号之中,[ -e “$FILE” ]就变成[ -e “” ],这会被判断为伪。 #!/bin/bash
该脚本用来发现输出损坏的链接。输出的结果是被引用的,
#+ 所以可以直接导到 xargs 中进行处理 :)
例如:sh broken-link.sh /somedir /someotherdir|xargs rm
更加优雅的方式:
find “somedir” -type 1 -print0|\
xargs -r0 file|\
grep “broken symbolic”|
sed -e ‘s/^|: broken symbolic.$/"/g’
但是这种方法不是纯 Bash 写法。
警告:小心 /proc 文件下的文件和任意循环链接!
############################################
如果不给脚本传任何参数,那么 directories-to-search 设置为当前目录
#+ 否则设置为传进的参数
#####################
[ KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲ -eq 0 ] && dir…@
函数 linkchk 是用来检测传入的文件夹中是否包含损坏的链接文件,
#+ 并引用输出他们。
如果文件夹中包含子文件夹,那么将子文件夹继续传给 linkchk 函数进行检测。
#################
linkchk () {
for element in
1
/
∗
;
d
o
[
−
h
"
1/*; do [ -h "
1/∗;do[−h"element" -a ! -e “KaTeX parse error: Expected 'EOF', got '&' at position 12: element" ] &̲& echo \"element”
[ -d “$element” ] && linkchk $element
# -h 用来检测是否是链接,-d 用来检测是否是文件夹。
done
}
检测传递给 linkchk() 函数的参数是否是一个存在的文件夹,
#+ 如果不是则报错。
################
for directory in $direcotrys; do
if [ -d $directory ]
then linkchk
d
i
r
e
c
t
o
r
y
e
l
s
e
e
c
h
o
"
directory else echo "
directoryelseecho"directory is not a directory"
echo “Usage $0 dir1 dir2 …”
fi
done
exit $?字符串判断[ string ]:如果string不为空(长度大于0),则判断为真。[ -n string ]:如果字符串string的长度大于零,则判断为真。[ -z string ]:如果字符串string的长度为零,则判断为真。[ string1 = string2 ]:如果string1和string2相同,则判断为真。[ string1 == string2 ] 等同于[ string1 = string2 ]。[ string1 != string2 ]:如果string1和string2不相同,则判断为真。[ string1 ‘>’ string2 ]:如果按照字典顺序string1排列在string2之后,则判断为真。[ string1 ‘<’ string2 ]:如果按照字典顺序string1排列在string2之前,则判断为真。注意,test命令内部的>和<,必须用引号引起来(或者是用反斜杠转义)。否则,它们会被 shell 解释为重定向操作符。 #!/bin/bash
ANSWER=maybe
if [ -z “KaTeX parse error: Expected 'EOF', got '&' at position 47: …s no answer." >&̲2 exit 1 fi i…ANSWER” = “yes” ]; then
echo “The answer is YES.”
elif [ “
A
N
S
W
E
R
"
=
"
n
o
"
]
;
t
h
e
n
e
c
h
o
"
T
h
e
a
n
s
w
e
r
i
s
N
O
.
"
e
l
i
f
[
"
ANSWER" = "no" ]; then echo "The answer is NO." elif [ "
ANSWER"="no"];thenecho"TheanswerisNO."elif["ANSWER” = “maybe” ]; then
echo “The answer is MAYBE.”
else
echo “The answer is UNKNOWN.”
fi首先确定
A
N
S
W
E
R
字符串是否为空。如果为空,就终止脚本,并把退出状态设为
1
。注意,这里的
e
c
h
o
命令把错误信息
T
h
e
r
e
i
s
n
o
a
n
s
w
e
r
.
重定向到标准错误,这是处理错误信息的常用方法
:
:
:
d
a
n
g
e
r
=
:
i
f
[
"
ANSWER字符串是否为空。如果为空,就终止脚本,并把退出状态设为1。 注意,这里的echo命令把错误信息There is no answer.重定向到标准错误,这是处理错误信息的常用方法:::danger =:if [ "
ANSWER字符串是否为空。如果为空,就终止脚本,并把退出状态设为1。注意,这里的echo命令把错误信息Thereisnoanswer.重定向到标准错误,这是处理错误信息的常用方法:::danger=:if["a" = “$b” ] 需要在=前后加上空格,负责就是赋值了。==:在(())和()里的表现不同。 :::[[ $a == z* ]] # $a 以 “z” 开头时为真(模式匹配)
[[ $a == “z*” ]] # $a 等于 z* 时为真(字符匹配)
[ KaTeX parse error: Expected 'EOF', got '#' at position 15: a == z* ] #̲ 发生文件匹配和字符分割。 […a" == “z*” ] # $a 等于 z* 时为真(字符匹配)
感谢 Stéphane Chazelas整数判断[ integer1 -eq integer2 ]:如果integer1等于integer2,则为true。[ integer1 -ne integer2 ]:如果integer1不等于integer2,则为true。[ integer1 -le integer2 ]:如果integer1小于或等于integer2,则为true。[ integer1 -lt integer2 ]:如果integer1小于integer2,则为true。[ integer1 -ge integer2 ]:如果integer1大于或等于integer2,则为true。[ integer1 -gt integer2 ]:如果integer1大于integer2,则为true。#!/bin/bash
INT=-5
if [ -z “$INT” ]; then #判断字符是否是空
echo “INT is empty.” >&2
exit 1
fi
if [ $INT -eq 0 ]; then
echo “INT is zero.”
else
if [ $INT -lt 0 ]; then
echo “INT is negative.”
else
echo “INT is positive.”
fi
if [
(
(
I
N
T
e
c
h
o
"
I
N
T
i
s
e
v
e
n
.
"
e
l
s
e
e
c
h
o
"
I
N
T
i
s
o
d
d
.
"
f
i
f
i
i
m
a
g
e
.
p
n
g
先判断变量
((INT % 2)) -eq 0 ]; then echo "INT is even." else echo "INT is odd." fi fiimage.png 先判断变量
((INTecho"INTiseven."elseecho"INTisodd."fifiimage.png先判断变量INT是否为空,然后判断是否为0,接着判断正负,最后通过求余数判断奇偶 . 正则判断[[ expression ]]这种判断形式,支持正则表达式。 [[ string1 =~ regex ]] #regex是一个正则表示式,=~是正则比较运算符
#!/bin/bash
INT=-5
if [[ “
I
N
T
"
=
−
?
[
0
−
9
]
+
INT" =~ ^-?[0-9]+
INT"= −?[0−9]+ ]]; then
echo “INT is an integer.”
exit 0
else
echo “INT is not an integer.” >&2
exit 1
fi判断标记 ”=~“先判断变量INT的字符串形式,是否满足^-?[0-9]+KaTeX parse error: Expected 'EOF', got '&' at position 114: …:dangerAND运算:符号&̲&,也可使用参数aOR运算:符…MIN_VAL,并且小于等于$MAX_VAL。使用否定操作符!时,最好用圆括号确定转义的范围。if [ ! ( $INT -ge $MIN_VAL -a $INT -le KaTeX parse error: Can't use function '\)' in math mode at position 9: MAX_VAL \̲)̲ ]; then ec…INT is outside $MIN_VAL to
M
A
X
V
A
L
.
"
e
l
s
e
e
c
h
o
"
MAX_VAL." else echo "
MAXVAL."elseecho"INT is in range.”
fi上面例子中,test命令内部使用的圆括号,必须使用引号或者转义,否则会被 Bash 解释。算术判断Bash 还提供了((…))作为算术条件,进行算术运算的判断。 if ((3 > 2)); then
echo “true”
fi
#代码执行后,会打印出true算术判断不需要使用test命令,而是直接使用((…))结构。这个结构的返回值,决定了判断的真伪。如果算术计算的结果是非零值,则表示判断成立。这一点跟命令的返回值正好相反,需要小心。 $ if ((1)); then echo “It is true.”; fi
It is true.
$ if ((0)); then echo “It is true.”; else echo “it is false.”; fi
It is false.
#((1))表示判断成立,((0))表示判断不成立算术条件((…))也可以用于变量赋值。 $ if (( foo = 5 ));then echo “foo is $foo”; fi
foo is 5(( foo = 5 ))完成了两件事情。首先把5赋值给变量foo,然后根据返回值5,判断条件为真。 注意,赋值语句返回等号右边的值,如果返回的是0,则判断为假。 $ if (( foo = 0 ));then echo “It is true.”;
else echo “It is false.”;
fi
It is false.用算术条件改写的数值判断脚本。 #!/bin/bash
INT=-5
if [[ "
I
N
T
"
=
−
?
[
0
−
9
]
+
INT" =~ ^-?[0-9]+
INT"= −?[0−9]+ ]]; then
if ((INT == 0)); then
echo “INT is zero.”
else
if ((INT < 0)); then
echo “INT is negative.”
else
echo “INT is positive.”
fi
if (( ((INT % 2)) == 0)); then
echo “INT is even.”
else
echo “INT is odd.”
fi
fi
else
echo “INT is not an integer.” >&2
exit 1
fi只要是算术表达式,都能用于((…))语法。[[ -d “KaTeX parse error: Expected 'EOF', got '&' at position 14: dir_name" ]] &̲& cd "dir_name” && rm *
等同于
if [[ ! -d “
d
i
r
n
a
m
e
"
]
]
;
t
h
e
n
e
c
h
o
"
N
o
s
u
c
h
d
i
r
e
c
t
o
r
y
:
′
dir_name" ]]; then echo "No such directory: '
dirname"]];thenecho"Nosuchdirectory:′dir_name’” >&2
exit 1
fi
if ! cd “
d
i
r
n
a
m
e
"
;
t
h
e
n
e
c
h
o
"
C
a
n
n
o
t
c
d
t
o
′
dir_name"; then echo "Cannot cd to '
dirname";thenecho"Cannotcdto′dir_name’” >&2
exit 1
fi
if ! rm *; then
echo “File deletion failed. Check results” >&2
exit 1
ficase(in) / esac 结构在 shell 脚本中,case 模拟了 C/C++ 语言中的 switch,可以根据条件跳转到其中一个分支。其相当于简写版的 if/then/else 语句。case结构用于多值判断,可以为每个值指定对应的命令,跟包含多个elif的if结构等价,但是语义更好: case expression in
pattern )
commands ;;
pattern )
commands ;;
…
esacexpression是一个表达式,pattern是表达式的值或者一个模式,可以有多条,用来匹配多个值,每条以两个分号(;)结尾。 :::danger对变量进行引用不是必须的,因为在这里不会进行字符分割。条件测试语句必须以右括号 ) 结束。每一段代码块都必须以双分号 ;; 结束。如果测试条件为真,其对应的代码块将被执行,而后整个 case 代码段结束执行。case 代码段必须以 esac 结束(倒着拼写case)。 :::#!/bin/bash
echo -n “输入一个数,在1-3之间 >”
read number
case $number in
1) echo 1
;;
2) echo 2
;;
3) echo 3
;;
) echo 输入不符合要求
esacimage.png最后一条匹配语句的模式是,这个通配符可以匹配其他字符和没有输入字符的情况,类似if的else部分。 判断当前是什么操作系统: #!/bin/bash
OS=$(uname -s)
case “$OS” in
FreeBSD) echo “This is FreeBSD” ;;
Darwin) echo “This is Mac OSX” ;;
AIX) echo “This is AIX” ;;
Minix) echo “This is Minix” ;;
Linux) echo “This is Linux” ;;
*) echo “Failed to identify this OS” ;;
esac:::danger case的匹配模式可以使用各种通配符:a):匹配a。a|b):匹配a或b。[[:alpha:]]):匹配单个字母。???):匹配3个字符的单词。.txt):匹配.txt结尾。):匹配任意输入,通过作为case结构的最后一个模式。 :::#!/bin/bash
echo -n "输入一个字母或数字 > "
read character
case $character in
[[:lower:]] | [[:upper:]] ) echo “输入了字母 $character”
;;
[0-9] ) echo “输入了数字 $character”
;;
- ) echo “输入不符合要求”
esac
#使用通配符[[:lower:]] | [[:upper:]]匹配字母,[0-9]匹配数字image.pngimage.png Bash 4.0之前,case结构只能匹配一个条件,然后就会退出case结构。Bash 4.0之后,允许匹配多个条件,这时可以用;;&终止每个条件块。 #!/bin/bash
test.sh
read -n 1 -p “Type a character > "
echo
case
R
E
P
L
Y
i
n
[
[
:
u
p
p
e
r
:
]
]
)
e
c
h
o
"
′
REPLY in [[:upper:]]) echo "'
REPLYin[[:upper:]])echo"′REPLY’ is upper case.” ;;&
[[:lower:]]) echo “‘KaTeX parse error: Expected 'EOF', got '&' at position 26: …lower case." ;;&̲ [[:alpha:]])…REPLY’ is alphabetic.” ;;&
[[:digit:]]) echo “‘KaTeX parse error: Expected 'EOF', got '&' at position 23: …is a digit." ;;&̲ [[:graph:]])…REPLY’ is a visible character.” ;;&
[[:punct:]]) echo “‘KaTeX parse error: Expected 'EOF', got '&' at position 36: …ion symbol." ;;&̲ [[:space:]])…REPLY’ is a whitespace character.” ;;&
[[:xdigit:]]) echo “‘$REPLY’ is a hexadecimal digit.” ;;&
esacimage.png 可以看到条件语句结尾添加了;;&以后,在匹配一个条件之后,并没有退出case结构,而是继续判断下一个条件。 样例 11-26. 使用 case 创建菜单 – select#!/bin/bash
简易的通讯录数据库
clear # 清屏。
echo " Contact List"
echo " ------- ----"
echo “Choose one of the following persons:”
echo
echo “[E]vans, Roland”
echo “[J]ones, Mildred”
echo “[S]mith, Julie”
echo “[Z]ane, Morris”
echo
read person
case “$person” in
“E” | “e” )
同时接受大小写的输入。
echo
echo “Roland Evans”
echo “4321 Flash Dr.”
echo “Hardscrabble, CO 80753”
echo “(303) 734-9874”
echo “(303) 734-9892 fax”
echo “revans@zzy.net”
echo “Business partner & old friend”
;;
“J” | “j” )
echo
echo “Mildred Jones”
echo “249 E. 7th St., Apt. 19”
echo “New York, NY 10009”
echo “(212) 533-2814”
echo “(212) 533-9972 fax”
echo “milliej@loisaida.com”
echo “Ex-girlfriend”
echo “Birthday: Feb. 11”
;;
- ) echo
echo “Not yet in database.”
;;
esac
exit 0用 case 来检测命令行参数。#!/bin/bash
E_CONFFILE=65
while [ $# -gt 0 ]; do # 遍历完所有参数
case “$1” in
-d|–debug)
# 检测是否是 “-d” 或者 “–debug”。
DEBUG=1
;;
-c|–conf)
CONFFILE=“$2”
shift
if [ ! -f $CONFFILE ]; then
echo “Error: Supplied file doesn’t exist!”
exit $E_CONFFILE # 找不到文件。
fi
;;
esac
shift # 检测下一个参数
done样例 11-28. 简单的字符串匹配#!/bin/bash
match-string.sh: 使用 ‘case’ 结构进行简单的字符串匹配。
match_string ()
{ # 字符串精确匹配。
MATCH=0
E_NOMATCH=90
PARAMS=2 # 需要2个参数。
E_BAD_PARAMS=91
[ $# -eq $PARAMS ] || return $E_BAD_PARAMS
case “$1” in
“$2”) return $MATCH;;
* ) return $E_NOMATCH;;
esac
}
a=one
b=two
c=three
d=two
match_string $a # 参数个数不够
echo $? # 91
match_string $a $b # 匹配不到
echo $? # 90
match_string $b $d # 匹配成功
echo $? # 0
exit 0for…in循环for…in循环用于遍历列表的每一项。 for arg in [list]语法格式:# [list] 是一个列表,类似list1 list2 list3 … listN
for arg in [list];do #如果do和for在同一行,则注意中间加个分号
command1
command2
…
done:::info一个单一变量也可以成为 for 循环中的 [list]【例题11-2】for 循环中的 [list] 可以是一个参数。 【例题1.1】for循环可以从函数获取[list] 【例题1-2】for 循环的结果可以通过管道导向至一个或多个命令中。 ::: 具体实例1-1,for 循环对范围的多种写法,【写出1-10】# 方式 1
for i in {1…10};do # 类似python中使用in的方式判断
echo “This is No $i”
done
方式 2
for((i=1;i<=10;i++));do # 类似C语言风格来实现
echo “This is No $i”
done
for ((a=1, b=1; a <= LIMIT ; a++, b++))
do # 逗号连接操作。
echo -n "
a
−
a-
a−b "
done
#方式3 使用seq
for a in seq 10
//1-10
do
echo -n "$a "
done
for i in
(
s
e
q
28
)
;
d
o
/
/
2
−
8
e
c
h
o
−
n
"
(seq 2 8); do //2-8 echo -n "
(seq28);do//2−8echo−n"i "
done参数作为listfor i in .png; do
ls -l $i
done
#.png会替换成当前目录中所有 PNG 图片文件,变量i会依次等于每一个文件
for file in *
#+ 会进行文件名扩展。[js]*,会自动匹配js开头的文件
do
ls -l “$file” # 列出 $PWD(当前工作目录)下的所有文件。
done
#!/bin/bash
count=0
for i in
(
c
a
t
/
.
b
a
s
h
p
r
o
f
i
l
e
)
;
d
o
c
o
u
n
t
=
(cat ~/.bash_profile); do count=
(cat /.bashprofile);docount=((count + 1))
echo “Word
c
o
u
n
t
(
count (
count(i) contains $(echo -n $i | wc -c) characters”
donecat /.bash_profile命令会输出/.bash_profile文件的内容,然后通过遍历每一个词,计算该文件一共包含多少个词,以及每个词有多少个字符。 样例 1-2. 从函数中获取List#!/bin/bash
function hello(){
echo 1 2 3
}
for i in $(hello)
do
echo -n $i # 123
done
exit 0样例 11-6. 缺少 in [list] 的 for 循环in list的部分可以省略,这时list默认等于脚本的所有参数
@
。但是,为了可读性,最好还是不要省略。
f
o
r
f
i
l
e
n
a
m
e
;
d
o
e
c
h
o
"
@。但是,为了可读性,最好还是不要省略。 for filename; do echo "
@。但是,为了可读性,最好还是不要省略。forfilename;doecho"filename"
done
等同于 # 缺失 ‘in list’ 的情况下,循环会遍历 ‘$@’
for filename in “
@
"
;
d
o
e
c
h
o
"
@" ; do echo "
@";doecho"filename”
done样例 11-2. for 循环 [list] 中的每一个变量有两个参数的情况#!/bin/bash
将每个行星与其到太阳的距离放在一起。
for planet in “Mercury 36” “Venus 67” “Earth 93” “Mars 142” “Jupiter 483”
do
set – $planet # 解析变量 “planet”
#+ 并将其每个部分赋值给位置参数。
“–” 防止一些极端情况,比如 $planet 为空或者以破折号开头。
因为位置参数会被覆盖掉,因此需要先保存原先的位置参数。
你可以使用数组来保存
original_params=(“$@”)
echo “$1 $2,000,000 miles from the sun”
#-------两个制表符—将后面的一系列 0 连到参数 $2 上。
done
exit 0样例 11-8. 一种替代 grep 搜索二进制文件的方法#!/bin/bash
E_BADARGS=66
E_NOFILE=65
if [ $# -ne 2 ]; then
echo “Usage basename $0
search_strings filename’”
exit $E_BADARGS
fi
if [ ! -f “$2” ]; then
echo “File “$2” is not exist.”
exit $E_NOFILE
fi
IFS=$‘\012’
for word in $( strings “$2” | grep “$1” )
do
echo $word
done
exit 0image.png样例 11-9. 列出系统中的所有用户#!/bin/bash
PASSWORD_FILE=/etc/passwd
n=1
for name in $(awk 'BEGIN{fs=“:”}{print KaTeX parse error: Expected 'EOF', got '}' at position 2: 1}̲' < "PASSWORD_FILE")
do
echo “USER #$n = $name”
let “n += 1”
done
exit $?image.png样例 11-10. 检查目录中所有二进制文件的原作者#!/bin/bash
directory=/usr/bin
fstring=“Free Software Foundation”
for file in $(find $directory -type f -name “*” | sort)
do
strings -f
f
i
l
e
∣
g
r
e
p
"
file | grep "
file∣grep"fstring" | sed -e “s%$directory%%”
done
exit $?image.png样例 11-11. 列出目录中的所有符号链接#!/bin/bash
directory=${1-pwd
} #缺省值,如果没有赋值就当前路径
for file in $( find KaTeX parse error: Expected 'EOF', got '#' at position 22: …ory -type l ) #̲查找l 链接文件 do …file"
done | sort
exit $?image.png将Link的输出结果重定向到文件里OUTFILE=symlinks.list
echo “Symbolic links in directory “
d
i
r
e
c
t
o
r
y
"
¨
>
"
directory\"" > "
directory"¨>"OUTFILE”
for file in $( find KaTeX parse error: Expected 'EOF', got '#' at position 22: …ory -type l ) #̲查找l 链接文件 do …file”
done | sort >> “$OUTFILE”
exit $?for循环for循环还支持 C 语言的循环语法。 for (( expression1; expression2; expression3 )); do
commands
done
for (( i=0; i<5; i=i+1 )); do
echo
i
d
o
n
e
注意,循环条件放在双重圆括号之中。另外,圆括号之中使用变量,不必加上美元符号
i done注意,循环条件放在双重圆括号之中。另外,圆括号之中使用变量,不必加上美元符号
idone注意,循环条件放在双重圆括号之中。另外,圆括号之中使用变量,不必加上美元符号。它等同于下面的while循环。(( expression1 ))
while (( expression2 )); do
commands
(( expression3 ))
donefor条件部分的三个语句,都可以省略。 for ((;😉)
do
read var
if [ “$var” = “.” ]; then
break
fi
donewhile循环语句while 循环结构会在循环顶部检测循环条件,若循环条件为真( 退出状态 为0)则循环持续进行。与 for循环 不同的是,while 循环是在不知道循环次数的情况下使用的。while循环有一个判断条件,只要符合条件,就不断循环执行指定的语句: 语法格式:while [ condition ]
do
command1
command2
…
done循环输出#!/bin/bash
i=1
LIMIT=10
while [ “
i
"
−
l
t
"
i" -lt "
i"−lt"LIMIT” ] #[] 不能使用< > >= <=
do
echo -n "$i "
let “i += 1”
done
while (( i <= LIMIT )) # 双圆括号结构,
do #+ 并且没有使用 "
"
。
e
c
h
o
−
n
"
"。 echo -n "
"。echo−n"a "
((i += 1)) # let “a+=1”
done
exit 0具体实例:计算两个数字之间的和echo “-------This program calculates the sum of all the numbers between two numbers-------”
echo "please inpput first number (need Must be less than the second number): "
read a
echo "Please input second number (Must be greater than the first number): "
read b
sum=0
while ((a <= b))
do
((sum+=a))
((a++))
done
echo "the plus is : $sum"循环条件condition可以使用test命令,跟if结构的判断条件写法一致。 #!/bin/bash
number=2
while [ “$number” -lt 10 ]; do
echo "Number =
n
u
m
b
e
r
"
n
u
m
b
e
r
=
number" number=
number"number=((number + 1))
done
#只要变量
n
u
m
b
e
r
小于
10
,
就会不断加
1
,
直到
number小于10,就会不断加1,直到
number小于10,就会不断加1,直到number等于10,然后退出循环image.png$ while true; do echo ‘Hi, while looping …’; donewhile的条件部分也可以是执行一个命令: $ while echo ‘ECHO’; do echo ‘Hi, while looping …’; done
#判断条件是echo ‘ECHO’,由于这个命令总是执行成功,所以产生无限循环样例 11-17. 多条件 while 循环#!/bin/bash
var1=hello
previous=$var1
while echo "previous-variable =
p
r
e
v
i
o
u
s
"
e
c
h
o
p
r
e
v
i
o
u
s
=
previous" echo previous=
previous"echoprevious=var1
[ “$var1” != end ] # 记录下 $var1 之前的值。
# 在 while 循环中有4个条件,但只有最后的那个控制循环。
do
echo “Input variable #1 (end to exit)”
read var1
echo “variable #1 = $var1”
done
exit 0while 循环可以调用函数。# while 函数生成条件
condition(){
((t++))
if [[ $t -le 5 ]]; then return 0 # true
else return 1 # false
fi
}
while condition
do
echo “Still going: t = $t”
done
exit 0while 与 read联合使用,用来循环读取值cat $filename | # 从文件获得输入。
while read line # 只要还有可以读入的行,循环就继续。
do
…
done在 while 循环后面可以通过 < 将标准输入 重定位到文件 中。 while 循环同样可以 通过管道 传入标准输入中。 until循环语句until循环与while循环恰好相反,只要不符合判断条件(判断条件失败),就不断循环执行指定的语句。一旦符合判断条件,就退出循环。 在循环的顶部判断条件,如果condition为False,就进入循环,和while语句相反。语法格式:until condition # 如果condition为True,直接结束循环
do
command1
command2
…
done
$ until false; do echo ‘Hi, until looping …’; done
Hi, until looping …
Hi, until looping …
Hi, until looping …
#until的部分一直为false,导致命令无限运行,必须按下 Ctrl + c 终止until的条件部分也可以是一个命令,表示在这个命令执行成功之前,不断重复尝试。 until cp $1 $2; do
echo ‘Attempt to copy failed. waiting…’
sleep 5
done
#只要cp $1 $2这个命令执行不成功,就5秒钟后再尝试一次,直到成功为止
#!/bin/bash
END_CONDITION=end
until [ “ v a r 1 " = " var1" = " var1"="END_CONDITION” ]
在循环顶部测试条件。
do
echo "Input variable #1 "
echo “($END_CONDITION to exit)”
read var1
echo “variable #1 = $var1”
echo
done
LIMIT=10
var=0
until (( var > LIMIT ))
do # ^^ ^ ^ ^^ 没有方括号,没有 $ 前缀。
echo -n "$var "
(( var++ ))
done # 0 1 2 3 4 5 6 7 8 9 10实例:计算1~50的乘积#程序使用C语言风格,如果使用shellcheck是会直接报错
set i=1
sum=0
until ((i>50))
do
((sum+=i))
((i++))
done
echo "The puls is : $sum"循环控制 continue/breakbreak 和 continue 命令的作用和在其他编程语言中的作用一样。break 用来中止(跳出)循环,而 continue 则是略过未执行的循环部分,直接进行下一次循环。样例 11-21. 循环中 break 与 continue 的作用#!/bin/bash
LIMIT=19 # 循环上界
echo
echo “Printing Numbers 1 through 20 (but not 3 and 11).”
a=0
while [
a
−
l
e
"
a -le "
a−le"LIMIT" ]
do
a=
(
(
((
((a+1))
if [ “
a
"
−
e
q
3
]
∣
∣
[
"
a" -eq 3 ] || [ "
a"−eq3]∣∣["a” -eq 11 ] # 除了 3 和 11。
then
continue # 略过本次循环的剩余部分。
fi
echo -n "$a " # 当 a 等于 3 和 11 时,将不会执行这条语句。
done
echo; echo
echo Printing Numbers 1 through 20, but something happens after 2.
##################################################################
用 ‘break’ 代替了 ‘continue’。
a=0
while [ “
a
"
−
l
e
"
a" -le "
a"−le"LIMIT” ]
do
a=
(
(
((
((a+1))
if [ “$a” -gt 2 ]
then
break # 中止循环。
fi
echo -n "$a "
done
echo;
exit 0:::danger break 命令接受一个参数。普通的 break 命令仅仅跳出其所在的那层循环,而 break N 命令则可以跳出其上 N 层的循环。 ::: 样例 11-22. 跳出多层循环#!/bin/bash
“break N” 跳出 N 层循环。
for outerloop in 1 2 3 4 5
do
echo -n "Group $outerloop: "
------------------------------------------
for innerloop in 1 2 3 4 5
do
echo -n "$innerloop "
if [ "$innerloop" -eq 3 ]
then
break 2 # 尝试一下 break 2 看看会发生什么。
# (它同时中止了内层和外层循环。)
fi
done
done
exit 0:::danger 与 break 类似,continue 也接受一个参数。普通的 continue 命令仅仅影响其所在的那层循环,而 continue N 命令则可以影响其上 N 层的循环。 ::: 样例 11-23. continue 影响外层循环#!/bin/bash
“continue N” 命令可以影响其上 N 层循环。
for outer in I II III IV V # 外层循环
do
echo; echo -n "Group $outer: "
--------------------------------------------------------------------
for inner in 1 2 3 4 5 6 7 8 9 10 # 内层循环
do
if [[ “KaTeX parse error: Expected 'EOF', got '&' at position 14: inner" -eq 7 &̲& "outer” = “III” ]]
then
continue 2 # 影响两层循环,包括“外层循环”。
# 将其替换为普通的 “continue”,那么只会影响内层循环。
fi
echo -n "$inner " # 7 8 9 10 将不会出现在 "Group III."中。
done
done
echo
exit 0image.pngselect 结构select结构主要用来生成简单的菜单。它的语法与for…in循环基本一致。 select name
[in list]
do
commands
break
done:::danger Bash 会对select依次进行下面的处理:select生成一个菜单,内容是列表list的每一项,并且每一项前面还有一个数字编号。Bash 提示用户选择一项,输入它的编号。用户输入以后,Bash 会将该项的内容存在变量name,该项的编号存入环境变量REPLY。如果用户没有输入,就按回车键,Bash 会重新输出菜单,让用户选择。执行命令体commands。执行结束后,回到第一步,重复这个过程。 :::#!/bin/bash
select brand in Samsung Sony iphone symphony Walton
do
echo “You have chosen $brand”
doneimage.pngimage.png 如果用户没有输入编号,直接按回车键。Bash 就会重新输出一遍这个菜单,直到用户按下Ctrl + c,退出执行。 select可以与case结合,针对不同项,执行不同的命令。 #!/bin/bash
echo “Which Operating System do you like?”
select os in Ubuntu LinuxMint Windows8 Windows10 WindowsXP
do
case $os in
“Ubuntu”|“LinuxMint”)
echo “I also use $os.”
;;
“Windows8” | “Windows10” | “WindowsXP”)
echo “Why don’t you try Linux?”
;;
*)
echo “Invalid entry.”
break
;;
esac
done
#case针对用户选择的不同项,执行不同的命令image.png样例 11-31. 在函数中使用 select 创建菜单#!/bin/bash
PS='Choose your favorite vegetable: ’
echo $PS
choice_of()
{
select vegetable # [in list] 被省略,因此 ‘select’ 将会使用传入函数的参数作为 list。
do
echo
echo “Your favorite veggie is $vegetable.”
echo “Yuck!”
echo
break
done
}
choice_of beans rice carrorts radishes rutabaga spinach
$1 $2 $3 $4 $5 $6
exit 0函数函数(function)是可以重复使用的代码片段,有利于代码的复用。它与别名(alias)的区别是,别名只适合封装简单的单个命令,函数则可以封装复杂的多行命令。函数总是在当前 Shell 执行,这是跟脚本的一个重大区别,Bash 会新建一个子 Shell 执行脚本。如果函数与脚本同名,函数会优先执行。但是,函数的优先级不如别名,即如果函数与别名同名,那么别名优先执行。Bash 函数定义的语法有两种:# 第一种
fn() {
codes
}
第二种
function fn() {
codes
}fn是自定义的函数名,函数代码就写在大括号之中。hello() {
echo “Hello $1” #$1表示函数调用时的第一个参数
}调用时,就直接写函数名,参数跟在函数名后面。 $ hello world
Hello worldimage.pngimage.png 删除一个函数,可以使用unset命令: unset -f functionName查看当前 Shell 已经定义的所有函数,可以使用declare命令。 $ declare -fdeclare命令不仅会输出函数名,还会输出所有定义。输出顺序是按照函数名的字母表顺序。由于会输出很多内容,最好通过管道命令配合more或less使用。 参数变量函数体内可以使用参数变量,获取函数参数。函数的参数变量,与脚本参数变量是一致的。 :::danger**$1**~$9:函数的第一个到第9个的参数。
0
∗
∗
:函数所在的脚本名。
∗
∗
0**:函数所在的脚本名。**
0∗∗:函数所在的脚本名。∗∗#:函数的参数总数。
@
∗
∗
:函数的全部参数,参数之间使用空格分隔。
∗
∗
@**:函数的全部参数,参数之间使用空格分隔。**
@∗∗:函数的全部参数,参数之间使用空格分隔。∗∗:函数的全部参数,参数之间使用变量*
I
F
S
∗
∗
值的第一个字符分隔,默认为空格,但是可以自定义。
:
:
:
如果函数的参数多于
9
个,那么第
10
个参数可以用
IFS**值的第一个字符分隔,默认为空格,但是可以自定义。 ::: 如果函数的参数多于9个,那么第10个参数可以用
IFS∗∗值的第一个字符分隔,默认为空格,但是可以自定义。:::如果函数的参数多于9个,那么第10个参数可以用{10}的形式引用,以此类推。#!/bin/bash
test.sh
function alice {
echo “alice: $@”
echo “$0: $1 $2 $3
4
"
e
c
h
o
"
4" echo "
4"echo"# arguments”
}
alice in wonderland一个日志函数: function log_msg {
echo “[date '+ %F %T'
]: $@”
}
$ log_msg “This is sample log message”
[ 2018-08-16 19:56:34 ]: This is sample log messagereturn 命令return命令用于从函数返回一个值。函数执行到这条命令,就不再往下执行了,直接返回了。 function func_return_value {
return 10
}函数将返回值返回给调用者。如果命令行直接执行函数,下一个命令可以用$?拿到返回值: $ func_return_value
$ echo “Value returned by function is: $?”
Value returned by function is: 10return后面不跟参数,只用于返回也是可以的。 function name {
commands
return
}全局变量和局部变量,local 命令在函数体内声明的变量是全局变量。 Bash 函数体内直接声明的变量,属于全局变量,整个脚本都可以读取。这一点需要特别小心。 # 脚本 test.sh
fn () {
foo=1
echo “fn: foo = $foo”
}
fn
echo "global: foo = $foo"函数体内不仅可以声明全局变量,还可以修改全局变量。 #! /bin/bash
foo=1
fn () {
foo=2
}
fn
echo KaTeX parse error: Expected 'EOF', got '#' at position 7: foo #̲输出的变量foo值为2函数里面可以用local命令声明局部变量: #! /bin/bash
脚本 test.sh
fn () {
local foo
foo=1
echo “fn: foo = $foo”
}
fn
echo “local vlaue: foo = $foo” #1 空,,
echo "global vlaue: foo = KaTeX parse error: Expected 'EOF', got '#' at position 8: foo" #̲1 在函数体内声明的变量是…foo变量,只在函数体内有效,函数体外没有定义。read —— 输入数据有时,脚本需要在执行过程中,由用户提供一部分数据,这时可以使用read命令。它将用户的输入存入一个变量,方便后面的代码使用。用户按下回车键,就表示输入结束。 read命令的格式如下。 read [-options] [variable…]options是参数选项,variable是用来保存输入数值的一个或多个变量名。如果没有提供变量名,环境变量REPLY会包含用户输入的一整行数据。 #!/bin/bash
echo -n "输入一些文本 > "
read text
echo "你的输入:$text"image.pngread可以接受用户输入的多个值: #!/bin/bash
echo “Please, enter your firstname and lastname”
read FN LN #read根据用户的输入,同时为两个变量赋值
echo "Hi! $LN, $FN !"image.png如果用户的输入项少于read命令给出的变量数目,那么额外的变量值为空。如果用户的输入项多于定义的变量,那么多余的输入项会包含到最后一个变量中。如果read命令之后没有定义变量名,那么环境变量REPLY会包含所有的输入。#!/bin/bash
read-single: read multiple values into default variable
echo -n "Enter one or more values > "
read
echo "REPLY = ‘$REPLY’"image.pngread命令除了读取键盘输入,可以用来读取文件。 #!/bin/bash
filename=‘/etc/hosts’
while read myline
do
echo “$myline”
done < $filename #重定向,如果不加的话,就无法输出内容读取文件内容,然后通过echo 输出到屏幕image.png上面的例子通过read命令,读取一个文件的内容。done命令后面的定向符<,将文件内容导向read命令,每次读取一行,存入变量myline,直到文件读取完毕。 参数read命令的参数如下:-t 参数,设置了超时的秒数。如果超过了指定时间,用户仍然没有输入,脚本将放弃等待,继续向下执行。#!/bin/bash
echo -n "输入一些文本 > "
if read -t 3 response; then
echo “用户已经输入了”
else
echo “用户没有输入”
fi
#输入命令会等待3秒,如果用户超过这个时间没有输入,这个命令就会执行失败image.png 环境变量TMOUT也可以起到同样作用,指定read命令等待用户输入的时间(单位为秒)。 $ TMOUT=3
$ read response
#等待3秒,如果用户还没有输入,就会超时-p 参数,指定用户输入的提示信息。 read -p "Enter one or more values > "
echo “REPLY = ‘$REPLY’”
#先显示Enter one or more values >,再接受用户的输入-a 参数,参数把用户的输入赋值给一个数组,从零号位置开始。 $ read -a people
alice duchess dodo
$ echo ${people[2]}
dodo
#用户输入被赋值给一个数组people,这个数组的2号成员就是dodo-n 参数,指定只读取若干个字符作为变量值,而不是整行读取。 $ read -n 3 letter
abcdefghij
$ echo $letter #letter只包含3个字母
abce 参数,允许用户输入的时候,使用readline库提供的快捷键,比如自动补全。 #!/bin/bash
echo Please input the path to the file:
read -e fileName
echo $fileName其他参数 :::danger d delimiter:定义字符串delimiter的第一个字符作为用户输入的结束,而不是一个换行符。 r:raw 模式,表示不把用户输入的反斜杠字符解释为转义字符。s:使得用户的输入不显示在屏幕上,这常常用于输入密码或保密信息。u fd:使用文件描述符fd作为输入。 ::: IFS 变量read命令读取的值,默认是以空格分隔。可以通过自定义环境变量IFS(内部字段分隔符,Internal Field Separator 的缩写),修改分隔标志。IFS的默认值是空格、Tab 符号、换行符号,通常取第一个(即空格)。如果把IFS定义成冒号(:)或分号(;),就可以分隔以这两个符号分隔的值,这对读取文件很有用。#!/bin/bash
read-ifs: read fields from a file
FILE=/etc/passwd
read -p “Enter a username > " user_name
file_info=”KaTeX parse error: Expected group after '^' at position 8: (grep "^̲user_name:" $FILE)"
if [ -n “
f
i
l
e
i
n
f
o
"
]
;
t
h
e
n
I
F
S
=
"
:
"
r
e
a
d
u
s
e
r
p
w
u
i
d
g
i
d
n
a
m
e
h
o
m
e
s
h
e
l
l
<
<
<
"
file_info" ]; then IFS=":" read user pw uid gid name home shell <<< "
fileinfo"];thenIFS=":"readuserpwuidgidnamehomeshell<<<"file_info”
echo “User = ‘
u
s
e
r
′
"
e
c
h
o
"
U
I
D
=
′
user'" echo "UID = '
user′"echo"UID=′uid’”
echo “GID = ‘
g
i
d
′
"
e
c
h
o
"
F
u
l
l
N
a
m
e
=
′
gid'" echo "Full Name = '
gid′"echo"FullName=′name’”
echo “Home Dir. = ‘
h
o
m
e
′
"
e
c
h
o
"
S
h
e
l
l
=
′
home'" echo "Shell = '
home′"echo"Shell=′shell’”
else
echo “No such user 'KaTeX parse error: Expected 'EOF', got '&' at position 14: user_name'" >&̲2 exit 1 fiIF…IFS”
IFS=“:”
read user pw uid gid name home shell <<< “
f
i
l
e
i
n
f
o
"
I
F
S
=
"
file_info" IFS="
fileinfo"IFS="OLD_IFS"另外,<<<是 Here 字符串,用于将变量值转为标准输入,因为read命令只能解析标准输入。 如果IFS设为空字符串,就等同于将整行读入一个变量。 #!/bin/bash
input=”/path/to/txt/file"
while IFS= read -r line
do
echo “
l
i
n
e
"
d
o
n
e
<
"
line" done < "
line"done<"input”
#逐行读取文件,每一行存入变量line,打印出来以后再读取下一行测试测试结构:::dangerif/then 结构是用来检测一系列命令的 退出状态 是否为0(按 UNIX 惯例,退出码 0 表示命令执行成功),如果为0,则执行接下来的一个或多个命令。测试结构会使用一个特殊的命令 [(参看特殊字符章节 左方括号)。等同于 test 命令,它是一个内建命令,写法更加简洁高效。该命令将其参数视为比较表达式或文件测试,以比较结果作为其退出状态码返回(0 为真,1 为假)。Bash 在 2.02 版本中引入了扩展测试命令 [[…]],它提供了一种与其他语言语法更为相似的方式进行比较操作。注意, [[ 是一个 关键字 而非一个命令。Bash 将 [[ $a -lt $b ]] 视为一整条语句,执行并返回退出状态。结构 (( … )) 和 let … 根据其执行的算术表达式的结果决定退出状态码。这样的 算术扩展 结构可以用来进行 数值比较。 ::: 注意,双括号算术扩展表达式的退出状态码不是一个错误的值。算术表达式为0,返回1;算术表达式不为0,返回0。var=-2 && (( var+=2 ))
echo $? # 1
var=-2 && (( var+=2 )) && echo $var
# 并不会输出 $var, 因为((var+=2))的状态码为1if 不仅可以用来测试括号内的条件表达式,还可以用来测试其他任何命令。if cmp a b &> /dev/null # 消去输出结果
then echo “Files a and b are identical.”
else echo “Files a and b differ.”
fi #Files a and b differ.
if-grep的组合
word=Linux
letter_sequence=inu
if echo “
w
o
r
d
"
∣
g
r
e
p
−
q
"
word" | grep -q "
word"∣grep−q"letter_sequence”
使用 -q 选项消去 grep 的输出结果
then
echo “$letter_sequence found in
w
o
r
d
"
e
l
s
e
e
c
h
o
"
word" else echo "
word"elseecho"letter_sequence not found in $word” #inu found in Linux
fi
#!/bin/bash
if [ “$false” ]
then
echo “”$false" is true."
else
echo “”$false" is false."
fi # “$false” 为假。
if [ “$true” ]
then
echo “”$true" is true."
else
echo “”$true" is false."
fi # “$false” 为假。
exit 0
“
f
a
l
s
e
"
i
s
f
a
l
s
e
.
"
false" is false. "
false"isfalse."true” is false.Shell进阶补充补充内部变量
R
A
N
D
O
M
:生成随机数
RANDOM:生成随机数
RANDOM:生成随机数RANDOM 是 Bash 中用来生成 0 至 32767 之间随机整数的一个内置 函数(而非常量)。其不应被用于生成密钥。#!/bin/bash
$RANDOM 每一次调用都会返回一个随机的不同的整数。
随机数的标称范围为 0 - 32767(16位有符号整型)。
MAXCOUNT=10
count=1
echo
echo “
M
A
X
C
O
U
N
T
r
a
n
d
o
m
n
u
m
b
e
r
s
:
"
e
c
h
o
"
−
−
−
−
−
−
−
−
−
−
−
−
−
−
−
−
−
"
w
h
i
l
e
[
"
MAXCOUNT random numbers:" echo "-----------------" while [ "
MAXCOUNTrandomnumbers:"echo"−−−−−−−−−−−−−−−−−"while["count” -le KaTeX parse error: Expected 'EOF', got '#' at position 17: …AXCOUNT ] #̲ 生成 10 (MAXCOUNT) 个随机整数。
do
number=$RANDOM
echo $number
let “count += 1” # 增加计数。
done
echo “-----------------”
如果你需要一个小于指定上界的随机数,可以使用 ‘modulo’ 操作符。
该操作符可以返回除法后的余数。
RANGE=500
echo
number=$RANDOM
let “number %= $RANGE”
^^
echo “Random number less than $RANGE — $number”
echo
如果你需要生成的随机数大于一个指定的下界,
#+ 可以增加一步判断,判别并丢弃所有小于下界的数。
FLOOR=200
number=0 # 初始化
while [ “$number” -le
F
L
O
O
R
]
d
o
n
u
m
b
e
r
=
FLOOR ] do number=
FLOOR]donumber=RANDOM
done
echo “Random number greater than $FLOOR — $number”
echo
现在来看一种可以代替上面循环的更简单的方式,也就是
let “number = $RANDOM + $FLOOR”
该方式可以不使用 while 循环,效率更高。
但是,该方法可能会产生一些问题,是什么呢?
通过结合上面的两种方法,可以获得一个特定范围内的随机数。
number=0 # 初始化
while [ “$number” -le
F
L
O
O
R
]
d
o
n
u
m
b
e
r
=
FLOOR ] do number=
FLOOR]donumber=RANDOM
let “number %= $RANGE” # 将 $number 缩小至 $RANGE 的范围内。
done
echo “Random number between $FLOOR and $RANGE — $number”
echo
生成二元选择值,即真(true)或假(false)。
BINARY=2
T=1
number=$RANDOM
let “number %= $BINARY”
如果使用 let “number >>= 14” 可以获得更优的随机分布
#+ (除了最低位,其余二进制位都右移)。
if [ “$number” -eq $T ]
then
echo “TRUE”
else
echo “FALSE”
fi
echo
扔一个骰子。
SPOTS=6 # 模 6 的余数范围为 0 - 5。
# 然后加 1 就可以得到期望的范围 1 - 6。
# 感谢 Paulo Marcel Coelho Aragao 简化了代码。
die1=0
die2=0
如果设置 SPOTS=7 就可以不用加 1 得到值。这是不是一种更好的方法,为什么?
为了保证公平,独立的投每一个骰子。
let "die1 = $RANDOM % $SPOTS + 1" # 投第一个骰子。
let "die2 = $RANDOM % $SPOTS + 1" # 投第二个骰子。
# 哪一种运算符有更高的优先级,
#+ 取余(%)还是加法(+)?
let “throw = $die1 + $die2”
echo “Throw of the dice = $throw”
echo
exit 0猜数字游戏:生成随机1-100的数,并判断。#!/bin/bash
function isnumber(){
echo $(expr match “$1” ‘[0-9]’)
}
random=$((RANDOM%100 + 1))
if [[ random%2 -eq 1 ]]; then echo “The $random is odd.”
else echo “The $random is even.”
fi
while [ 1 ]; do
read -p "Please input a number[between 1 and 100]: " number
if [ -z “$number” ]; then echo “Input is NULL, Please input a number!”
else
if [[ $(isnumber $number) -ne 0 ]]; then
if [[ $number -lt $random ]]; then echo “It is too small.”
elif [[ $number -gt $random ]]; then echo “It is too big.”
else echo “You are right.” ; break
fi
else echo “Input is error. Please input a number!”;
fi
fi
done
exit 0命令转换命令替换重新指定一个或多个命令的输出。其实就是将命令的输出导到另外一个地方。命令替换的通常形式是(…),即用反引号引用命令。script_name=basename $0
echo “The name of this script is $scirpt_name.”:::danger 命令替换有可能会出现 字符分割 的情况。 :::COMMAND echo a b
# 2个参数:a和b
COMMAND “echo a b
” # 1个参数:“a b”
COMMAND echo
# 没有参数
COMMAND “echo
” # 一个空参数:::danger 使用 echo 输出未被引用的命令代换的变量时会删掉尾部的换行。这可能会导致非常不好的情况出现。 :::dir_listing=ls -l
echo $dir_listing # 未被引用
你希望会出现按行显示出文件列表。
但是,你却看到了:
total 3 -rw-rw-r-- 1 bozo bozo 30 May 13 17:15 1.txt -rw-rw-r-- 1 bozo
bozo 51 May 15 20:57 t2.sh -rwxr-xr-x 1 bozo bozo 217 Mar 5 21:13 wi.sh
所有换行都消失了。
echo “$dir_listing” # 被引用
-rw-rw-r-- 1 bozo 30 May 13 17:15 1.txt
-rw-rw-r-- 1 bozo 51 May 15 20:57 t2.sh
-rwxr-xr-x 1 bozo 217 Mar 5 21:13 wi.sh你甚至可以使用 重定向 或者 cat 命令把一个文件的内容通过命令代换赋值给一个变量。variable1=<file1
# 将 “file1” 的内容赋值给 variable1。
variable2=cat file2
# 将 “file2” 的内容赋值给 variable2。
# 使用 cat 命令会开一个新进程,因此执行速度会比重定向慢。
需要注意的是,这些变量中可能包含一些空格或者控制字符。
无需显示的赋值给一个变量。
echo " <$0
" # 输出脚本自身。:::danger 命令替换能够让 Bash 做更多的事情。而这仅仅需要在书写程序或者脚本时将结果输出到标准输出 stdout 中,然后将这些输出结果赋值给变量即可。 ::: image.png :::danger 在命令替换中,你可以使用 $(…) 来替代反引号。 ::: 快捷操作 — 行操作Bash内置了Readline库,具有这个库提供的很多“行操作”功能,比如命令的自动补全,可以大大加快操作速度。默认采用 Emacs 快捷键,也可以改成 Vi 快捷键。 $ set -o vi
$ set -o emacs #改回 Emacs 快捷键如果想永久性更改编辑模式(Emacs / Vi),可以将命令写在~/.inputrc文件,这个文件是 Readline 的配置文件。set editing-mode viBash 默认开启这个库,但是允许关闭。 $ bash --noediting #–noediting参数关闭了 Readline 库下面介绍的快捷键都属于 Emacs 模式。光标移动Readline 提供快速移动光标的快捷键: :::dangerCtrl + a:移到行首。Ctrl + b:向行首移动一个字符,与左箭头作用相同。Ctrl + e:移到行尾。Ctrl + f:向行尾移动一个字符,与右箭头作用相同。Alt + f:移动到当前单词的词尾。Alt + b:移动到当前单词的词首。 ::: 上面快捷键的 Alt 键,也可以用 ESC 键代替。 清除屏幕Ctrl + l快捷键可以清除屏幕,即将当前行移到屏幕的第一行,与clear命令作用相同。 Shell环境和除错set、shoptBash 执行脚本时,会创建一个子 Shell: $ bash script.sh上面代码中,script.sh是在一个子Shell 里面执行。这个子 Shell 就是脚本的执行环境,Bash 默认给定了这个环境的各种参数 set命令用来修改子 Shell 环境的运行参数,即定制环境,一共有十几个参数可以定制。如果命令行下不带任何参数,直接运行set,会显示所有的环境变量和 Shell 函数。 set -u执行脚本时,如果遇到不存在的变量,Bash 默认忽略它: #!/usr/bin/env bash
echo a e c h o b a r a echo bar aechobara是一个不存在的变量,执行结果如下: $ bash script.sh
barecho
a
输出了一个空行,
B
a
s
h
忽略了不存在的
a输出了一个空行,Bash 忽略了不存在的
a输出了一个空行,Bash忽略了不存在的a,然后继续执行echo bar。大多数情况下,这不是开发者想要的行为,遇到变量不存在,脚本应该报错,而不是一声不响地往下执行。 set -u就用来改变这种行为。脚本在头部加上它,遇到不存在的变量就会报错,并停止执行。 #!/usr/bin/env bash
set -u
echo $a
echo bar运行结果如下: $ bash script.sh
bash: script.sh:行4: a: 未绑定的变量可以看到,脚本报错了,并且不再执行后面的语句。-u还有另一种写法o nounset,两者是等价的:set -o nounsetset -x默认情况下,脚本执行后,只输出运行结果,没有其他内容。如果多个命令连续执行,它们的运行结果就会连续输出。有时会分不清,某一段内容是什么命令产生的。 set -x用来在运行结果之前,先输出执行的那一行命令。 #!/usr/bin/env bash
set -x
echo bar
$ bash script.sh
- echo bar #指明是有这条指令产生的结果
bar执行echo bar之前,该命令会先打印出来,行首以+表示。这对于调试复杂的脚本是很有用的。 -x还有另一种写法o xtrace。 set -o xtrace脚本当中如果要关闭命令输出,可以使用set +x。 #!/bin/bash
number=1
set -x
if [ $number = “1” ]; then
echo “Number equals 1”
else
echo “Number does not equal 1”
fi
set +xBash 的错误处理如果脚本里面有运行失败的命令(返回值非0),Bash 默认会继续执行后面的命令。 #!/usr/bin/env bash
foo
echo bar上面脚本中,foo是一个不存在的命令,执行时会报错。但是,Bash 会忽略这个错误,继续往下执行。 $ bash script.sh
script.sh:行3: foo: 未找到命令
bar
#Bash 只是显示有错误,并没有终止执行这种行为很不利于脚本安全和除错。实际开发中,如果某个命令失败,往往需要脚本停止执行,防止错误累积。这时,一般采用下面的写法。 command || exit 1 #只要command有非零返回值,脚本就会停止执行如果停止执行之前需要完成多个操作,就要采用下面三种写法。 # 写法一
command || { echo “command failed”; exit 1; }
写法二
if ! command; then echo “command failed”; exit 1; fi
写法三
command
if [ “$?” -ne 0 ]; then echo “command failed”; exit 1; fiset -e上面这些写法多少有些麻烦,容易疏忽。set -e从根本上解决了这个问题,它使得脚本只要发生错误,就终止执行。 #!/usr/bin/env bash
set -e
foo
echo bar
$ bash script.sh
script.sh:行4: foo: 未找到命令set -e根据返回值来判断,一个命令是否运行失败。但是,某些命令的非零返回值可能不表示失败,或者开发者希望在命令失败的情况下,脚本继续执行下去。这时可以暂时关闭set -e,该命令执行结束后,再重新打开set -e。 set +e
command1
command2
set -eset +e表示关闭-e选项,set -e表示重新打开-e选项 还有一种方法是使用command || true,使得该命令即使执行失败,脚本也不会终止执行。 #!/bin/bash
set -e
foo || true
echo bar上面代码中,true使得这一行语句总是会执行成功,后面的echo bar会执行。-e还有另一种写法o errexit。set -o errexitset -o pipefailset -e有一个例外情况,就是不适用于管道命令。所谓管道命令,就是多个子命令通过管道运算符(|)组合成为一个大的命令。Bash 会把最后一个子命令的返回值,作为整个命令的返回值。也就是说,只要最后一个子命令不失败,管道命令总是会执行成功,因此它后面命令依然会执行,set -e就失效了。#!/usr/bin/env bash
set -e
foo | echo a
echo bar
$ bash script.sh
a
script.sh:行4: foo: 未找到命令
barfoo是一个不存在的命令,但是foo | echo a这个管道命令会执行成功,导致后面的echo bar会继续执行。 set -o pipefail用来解决这种情况,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。 #!/usr/bin/env bash
set -eo pipefail
foo | echo a
echo bar
$ bash script.sh
a
script.sh:行4: foo: 未找到命令set -E一旦设置了-e参数,会导致函数内的错误不会被trap命令捕获。-E参数可以纠正这个行为,使得函数也能继承trap命令。 #!/bin/bash
set -e
trap “echo ERR trap fired!” ERR
myfunc()
{
‘foo’ 是一个不存在的命令
foo
}
myfunc上面示例中,myfunc函数内部调用了一个不存在的命令foo,导致执行这个函数会报错。 $ bash test.sh
test.sh:行9: foo:未找到命令但是,由于设置了set -e,函数内部的报错并没有被trap命令捕获,需要加上-E参数才可以。 #!/bin/bash
set -Eeuo pipefail
trap “echo ERR trap fired!” ERR
myfunc()
{
‘foo’ 是一个不存在的命令
foo
}
myfunc执行上面这个脚本,就可以看到trap命令生效了。 $ bash test.sh
test.sh:行9: foo:未找到命令
ERR trap fired!set命令还有一些其他参数。set -n:等同于set -o noexec,不运行命令,只检查语法是否正确。set -f:等同于set -o noglob,表示不对通配符进行文件名扩展。set -v:等同于set -o verbose,表示打印 Shell 接收到的每一行输入。set -o noclobber:防止使用重定向运算符>覆盖已经存在的文件。上面的-f和-v参数,可以分别使用set +f、set +v关闭。 set 命令总结重点介绍的set命令的几个参数,一般都放在一起使用。 # 写法一
set -Eeuxo pipefail
写法二
set -Eeux
set -o pipefail这两种写法建议放在所有 Bash 脚本的头部。 另一种办法是在执行 Bash 脚本的时候,从命令行传入这些参数。 $ bash -euxo pipefail script.shshopt 命令shopt命令用来调整 Shell 的参数,跟set命令的作用很类似。之所以会有这两个类似命令的主要原因是,set是从 Ksh 继承的,属于 POSIX 规范的一部分,而shopt是 Bash 特有的。直接输入shopt可以查看所有参数,以及它们各自打开和关闭的状态。$ shoptimage.pngshopt命令后面跟着参数名,可以查询该参数是否打开。 $ shopt globstar
globstar off #表示globstar参数默认是关闭的
$ shopt -s optionNameHere #-s用来打开某个参数
$ shopt -u optionNameHere #-u用来关闭某个参数举例来说,histappend这个参数表示退出当前 Shell 时,将操作历史追加到历史文件中。这个参数默认是打开的,如果使用下面的命令将其关闭,那么当前 Shell 的操作历史将替换掉整个历史文件。 $ shopt -u histappend-q的作用也是查询某个参数是否打开,但不是直接输出查询结果,而是通过命令的执行状态($?)表示查询结果。如果状态为0,表示该参数打开;如果为1,表示该参数关闭。 $ shopt -q globstar
$ echo $?
1上面命令查询globstar参数是否打开。返回状态为1,表示该参数是关闭的。这个用法主要用于脚本,供if条件结构使用。 #如果打开了这个参数,就执行if结构内部的语句:
if (shopt -q globstar); then
…脚本除错编写 Shell 脚本的时候,一定要考虑到命令失败的情况,否则很容易出错。#! /bin/bash
dir_name=/path/not/exist
cd
d
i
r
n
a
m
e
r
m
∗
如果目录
dir_name rm *如果目录
dirnamerm∗如果目录dir_name不存在,cd $dir_name命令就会执行失败。这时,就不会改变当前目录,脚本会继续执行下去,导致rm *命令删光当前目录的文件。 [[ -d $dir_name ]] && cd KaTeX parse error: Expected 'EOF', got '&' at position 10: dir_name &̲& rm * #先判断目录dir_name是否存在,然后才执行其他操作如果不放心删除什么文件,可以先打印出来看一下。 [[ -d $dir_name ]] && cd $dir_name && echo rm * #echo rm *不会删除文件,只会打印出来要删除的文件bash的-x参数bash的-x参数可以在执行每一行命令之前,打印该命令。一旦出错,这样就比较容易追查。 # script.sh
echo hello world加上-x参数,执行每条命令之前,都会显示该命令。 $ bash -x script.sh
- echo hello world
hello worldimage.png 行首为+的行,显示该行是所要执行的命令,下一行才是该命令的执行结果。 输出的命令之前的+号,是由系统变量PS4决定,可以修改这个变量。 $ export PS4='$LINENO + ’
$ trouble
5 + number=1
7 + ‘[’ 1 = 1 ‘]’
8 + echo ‘Number is equal to 1.’
Number is equal to 1.另外,set命令也可以设置 Shell 的行为参数,有利于脚本除错。 环境变量有一些环境变量常用于除错。 LINENO——返回它在脚本里面的行号#!/bin/bash
echo “This is line $LINENO”
$ ./test.sh
This is line 3FUNCNAME——返回数组,当前的函数调用堆栈变量FUNCNAME返回一个数组,内容是当前的函数调用堆栈。该数组的0号成员是当前调用的函数,1号成员是调用当前函数的函数,以此类推。#!/bin/bash
function func1()
{
echo “func1: FUNCNAME0 is ${FUNCNAME[0]}”
echo “func1: FUNCNAME1 is ${FUNCNAME[1]}”
echo “func1: FUNCNAME2 is ${FUNCNAME[2]}”
func2
}
function func2()
{
echo “func2: FUNCNAME0 is ${FUNCNAME[0]}”
echo “func2: FUNCNAME1 is ${FUNCNAME[1]}”
echo “func2: FUNCNAME2 is ${FUNCNAME[2]}”
}
func1image.png 执行func1时,变量FUNCNAME的0号成员是func1,1号成员是调用func1的主脚本main。执行func2时,变量FUNCNAME的0号成员是func2,1号成员是调用func2的func1。BASH_SOURCE——当前的脚本调用堆栈变量BASH_SOURCE返回一个数组,内容是当前的脚本调用堆栈。该数组的0号成员是当前执行的脚本,1号成员是调用当前脚本的脚本,以此类推,跟变量FUNCNAME是一一对应关系。下面有两个子脚本lib1.sh和lib2.sh。# lib1.sh
function func1()
{
echo “func1: BASH_SOURCE0 is ${BASH_SOURCE[0]}”
echo “func1: BASH_SOURCE1 is ${BASH_SOURCE[1]}”
echo “func1: BASH_SOURCE2 is ${BASH_SOURCE[2]}”
func2
}
lib2.sh
function func2()
{
echo “func2: BASH_SOURCE0 is ${BASH_SOURCE[0]}”
echo “func2: BASH_SOURCE1 is ${BASH_SOURCE[1]}”
echo “func2: BASH_SOURCE2 is ${BASH_SOURCE[2]}”
}然后,主脚本main.sh调用上面两个子脚本。 #!/bin/bash
main.sh
source lib1.sh
source lib2.sh
func1执行主脚本main.sh,会得到下面的结果。 $ ./main.sh
func1: BASH_SOURCE0 is lib1.sh
func1: BASH_SOURCE1 is ./main.sh
func1: BASH_SOURCE2 is
func2: BASH_SOURCE0 is lib2.sh
func2: BASH_SOURCE1 is lib1.sh
func2: BASH_SOURCE2 is ./main.sh执行函数func1时,变量BASH_SOURCE的0号成员是func1所在的脚本lib1.sh,1号成员是主脚本main.sh;执行函数func2时,变量BASH_SOURCE的0号成员是func2所在的脚本lib2.sh,1号成员是调用func2的脚本lib1.sh。 BASH_LINENO—— 每一轮调用对应的行号变量BASH_LINENO返回一个数组,内容是每一轮调用对应的行号。KaTeX parse error: Expected '}', got 'EOF' at end of input: {BASH_LINENO[i]}跟KaTeX parse error: Expected '}', got 'EOF' at end of input: {FUNCNAME[i]}是一一对应关系,表示KaTeX parse error: Expected '}', got 'EOF' at end of input: {FUNCNAME[i]}在调用它的脚本文件KaTeX parse error: Expected '}', got 'EOF' at end of input: {BASH_SOURCE[i+1]}里面的行号。下面有两个子脚本lib1.sh和lib2.sh。# lib1.sh
function func1()
{
echo “func1: BASH_LINENO is ${BASH_LINENO[0]}”
echo “func1: FUNCNAME is ${FUNCNAME[0]}”
echo “func1: BASH_SOURCE is ${BASH_SOURCE[1]}”
func2
}
lib2.sh
function func2()
{
echo “func2: BASH_LINENO is ${BASH_LINENO[0]}”
echo “func2: FUNCNAME is ${FUNCNAME[0]}”
echo “func2: BASH_SOURCE is ${BASH_SOURCE[1]}”
}然后,主脚本main.sh调用上面两个子脚本。 #!/bin/bash
main.sh
source lib1.sh
source lib2.sh
func1
$ ./main.sh
func1: BASH_LINENO is 7
func1: FUNCNAME is func1
func1: BASH_SOURCE is main.sh
func2: BASH_LINENO is 8
func2: FUNCNAME is func2
func2: BASH_SOURCE is lib1.sh函数func1是在main.sh的第7行调用,函数func2是在lib1.sh的第8行调用的。嵌入Shell变量——CDPATHBrourne shell 有一些预留的环境变量名,这些变量名不能用作其他用途。通常在/etc/profile中建立这些嵌入的环境变量,但也不完全是,这取决于用户自己。 CDPATH:改变目录路径变量,保留一系列由冒号隔开的路径名,用于cd命令。如果设置了CDPATH,cd一个目录时,首先查找CDPATH,如果CDPATH指明此目录,则此目录成为当前工作目录。 如果你经常使用linux cd命令进入特定父目录下的子目录, 你可将该父目录设置为CDPATH, 执行linux cd命令进入子目录时无需再指定父目录路径. 讲解如下:image.png[ljf@localhost ~]$ pwd
/home/ljf
[ljf@localhost ~]$ cd mail
-bash: cd: mail: No such file or directory
[ljf@localhost ~]$ export CDPATH=/etc
[ljf@localhost ~]$ cd mail
/etc/mail简单来说,CDPATH是可以手动设置当前目录的路径,这样就不用一直cd来进入到子目录中了。 mktemp、trap直接创建临时文件,尤其在/tmp目录里面,往往会导致安全问题。首先,/tmp目录是所有人可读写的,任何用户都可以往该目录里面写文件。创建的临时文件也是所有人可读的。 [root@VM-16-3-centos ~]# touch /tmp/info.txt #在/tmp目录直接创建文件,该文件默认是所有人可读的
[root@VM-16-3-centos ~]# ls -l /tmp/info.txt
-rw-r–r-- 1 root root 0 Jan 15 19:14 /tmp/info.txt其次,如果攻击者知道临时文件的文件名,他可以创建符号链接,链接到临时文件,可能导致系统运行异常。攻击者也可能向脚本提供一些恶意数据。因此,临时文件最好使用不可预测、每次都不一样的文件名,防止被利用。最后,临时文件使用完毕,应该删除。但是,脚本意外退出时,往往会忽略清理临时文件。生成临时文件应该遵循下面的规则。 :::danger创建前检查文件是否已经存在确保临时文件已成功创建临时文件必须有权限的限制临时文件要使用不可预测的文件名脚本退出时,要删除临时文件(使用trap命令) ::: mktemp 命令——安全创建临时文件mktemp命令就是为安全创建临时文件而设计的。虽然在创建临时文件之前,它不会检查临时文件是否存在,但是它支持唯一文件名和清除机制,因此可以减轻安全攻击的风险。直接运行mktemp命令,就能生成一个临时文件。[root@VM-16-3-centos ~]# mktemp
/tmp/tmp.1YTYweswOG
[root@VM-16-3-centos ~]# ls -l /tmp/tmp.1YTYweswOG
-rw------- 1 root root 0 Jan 15 19:29 /tmp/tmp.1YTYweswOG上面命令中,mktemp命令生成的临时文件名是随机的,而且权限是只有用户本人可读写。 Bash 脚本使用mktemp命令的用法如下。 #!/bin/bash
TMPFILE=$(mktemp)
echo "Our temp file is $TMPFILE"为了确保临时文件创建成功,mktemp命令后面最好使用 OR 运算符(||),保证创建失败时退出脚本。 #!/bin/bash
TMPFILE=$(mktemp) || exit 1
echo "Our temp file is $TMPFILE"为了保证脚本退出时临时文件被删除,可以使用trap命令指定退出时的清除操作。 #!/bin/bash
trap ‘rm -f “$TMPFILE”’ EXIT
TMPFILE=$(mktemp) || exit 1
echo "Our temp file is
T
M
P
F
I
L
E
"
m
k
t
e
m
p
命令的参数
−
d
参数可以创建一个临时目录。
TMPFILE"mktemp 命令的参数-d参数可以创建一个临时目录。
TMPFILE"mktemp命令的参数−d参数可以创建一个临时目录。 mktemp -d
/tmp/tmp.Wcau5UjmN6-p参数可以指定临时文件所在的目录。默认是使用$TMPDIR环境变量指定的目录,如果这个变量没设置,那么使用/tmp目录。 $ mktemp -p /home/ruanyf/
/home/ruanyf/tmp.FOKEtvs2H3-t参数可以指定临时文件的文件名模板,模板的末尾必须至少包含三个连续的X字符,表示随机字符,建议至少使用六个X。默认的文件名模板是tmp.后接十个随机字符。 $ mktemp -t mytemp.XXXXXXX
/tmp/mytemp.yZ1HgZVtrap 命令——响应系统信号trap命令用来在Bash脚本中响应系统信号。最常见的系统信号就是 SIGINT(中断),即按Ctrl + C所产生的信号。trap命令的-l参数,可以列出所有的系统信号: $ trap -l
- SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
- SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
- SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
- SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
- SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
- SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
- SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
- SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
- SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
- SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
- SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
- SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
- SIGRTMAX-1 64) SIGRTMAXimage.pngtrap的命令格式如下: $ trap [动作] [信号1] [信号2] …上面代码中,“动作”是一个 Bash 命令,“信号”常用的有以下几个:HUP:编号1,脚本与所在的终端脱离联系INT:编号2,用户按下 Ctrl + C,意图让脚本终止运行QUIT:编号3,用户按下 Ctrl + 斜杠,意图退出脚本KILL:编号9,该信号用于杀死进程TERM:编号15,这是kill命令发出的默认信号EXIT:编号0,这不是系统信号,而是 Bash 脚本特有的信号,不管什么情况,只要退出脚本就会产生。trap命令响应EXIT信号的写法如下:$ trap 'rm -f "KaTeX parse error: Expected 'EOF', got '#' at position 17: …MPFILE"' EXIT #̲脚本遇到EXIT信号时,执行r…TMPFILE"trap命令的常见使用场景,就是在Bash脚本中指定退出时执行的清理命令 #!/bin/bash
trap ‘rm -f “$TMPFILE”’ EXIT
TMPFILE=$(mktemp) || exit 1
ls /etc > $TMPFILE
if grep -qi “kernel” $TMPFILE; then
echo ‘find’
fi不管是脚本正常执行结束,还是用户按Ctrl + C终止,都会产生EXIT信号,从而触发删除临时文件。注:trap命令必须放在脚本的开头。否则,它上方的任何命令导致脚本退出,都不会被它捕获。 如果trap需要触发多条命令,可以封装一个Bash函数: function egress {
command1
command2
command3
}
trap egress EXITBash 启动环境用户每次使用 Shell,都会开启一个与 Shell 的 Session(对话)。Session有两种类型:登录Session 和非登录 Session,也可以叫做 login shell 和 non-login shell。 登录 Session登录Session是用户登录系统以后,系统为用户开启的原始Session,通常需要用户输入用户名和密码进行登录。登录Session 一般进行整个系统环境的初始化,启动的初始化脚本依次如下:/etc/profile:所有用户的全局配置脚本/etc/profile.d目录里面所有.sh文件/.bash_profile:用户的个人配置脚本。如果该脚本存在,则执行完就不再往下执行。/.bash_login:如果~/.bash_profile没找到,则尝试执行这个脚本(C shell 的初始化脚本)。如果该脚本存在,则执行完就不再往下执行/.profile:如果/.bash_profile和~/.bash_login都没找到,则尝试读取这个脚本(Bourne shell 和 Korn shell 的初始化脚本)Linux发行版更新的时候,会更新/etc里面的文件,比如/etc/profile,因此不要直接修改这个文件。如果想修改所有用户的登陆环境,就在/etc/profile.d目录里面新建.sh脚本。 如果想修改个人的登录环境,一般是写在~/.bash_profile里面。下面是一个典型的.bash_profile文件。 # .bash_profile
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
PATH=
P
A
T
H
:
PATH:
PATH:HOME/bin
SHELL=/bin/bash
MANPATH=/usr/man:/usr/X11/man
EDITOR=/usr/bin/vi
PS1=‘\h:\w$ ’
PS2=’> ’
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
export PATH
export EDITOR可以看到,这个脚本定义了一些最基本的环境变量,然后执行了~/.bashrc。bash命令的–login参数,会强制执行登录 Session 会执行的脚本。$ bash --loginbash命令的–noprofile参数,会跳过上面这些 Profile 脚本。 $ bash --noprofile非登录 Session非登录 Session 是用户进入系统以后,手动新建的 Session,这时不会进行环境初始化。比如,在命令行执行bash命令,就会新建一个非登录 Session。非登录 Session 的初始化脚本依次如下:/etc/bash.bashrc:对全体用户有效/.bashrc:仅对当前用户有效对用户来说,/.bashrc通常是最重要的脚本。非登录 Session 默认会执行它,而登录 Session一般也会通过调用执行它。每次新建一个 Bash 窗口,就相当于新建一个非登录 Session,所以~/.bashrc每次都会执行。注意,执行脚本相当于新建一个非互动的 Bash 环境,但是这种情况不会调用~/.bashrc。bash命令的–norc参数,可以禁止在非登录 Session 执行~/.bashrc脚本。$ bash --norcbash命令的–rcfile参数,指定另一个脚本代替.bashrc。 $ bash --rcfile testrc.bash_logout~/.bash_logout脚本在每次退出 Session 时执行,通常用来做一些清理工作和记录工作,比如删除临时文件,记录用户在本次 Session 花费的时间。如果没有退出时要执行的命令,这个文件也可以不存在。启动选项为了方便 Debug,有时在启动 Bash 的时候,可以加上启动参数。-n:不运行脚本,只检查是否有语法错误-v:输出每一行语句运行结果前,会先输出该行语句-x:每一个命令处理之前,先输出该命令,再执行该命令$ bash -n scriptname
$ bash -v scriptname
$ bash -x scriptname键盘绑定Bash允许用户定义自己的快捷键。全局的键盘绑定文件默认为/etc/inputrc,可以在主目录创建自己的键盘绑定文件.inputrc文件。如果定义了这个文件,需要在其中加入下面这行,保证全局绑定不会被遗漏。 KaTeX parse error: Undefined control sequence: \C at position 47: …面的快捷键,可以像这样定义,"\̲C̲-t":"pwd\n"表示将C…,对于根用户则是井号#。这个符号是环境变量PS1决定的,查看当前命令提示符的定义: $ echo $PS1
[\u@\h \W]$Bash 允许用户自定义命令提示符,只要改写这个变量即可。改写后的PS1,可以放在用户的 Bash 配置文件.bashrc里面,以后新建 Bash 对话时,新的提示符就会生效。要在当前窗口看到修改后的提示符,可以执行下面的命令。 $ source ~/.bashrc命令提示符的定义,可以包含特殊的转义字符,表示特定内容。 :::danger\a:响铃,计算机发出一记声音。\d:以星期、月、日格式表示当前日期,例如“Mon May 26”。\h:本机的主机名。\H:完整的主机名。\j:运行在当前 Shell 会话的工作数。\l:当前终端设备名。\n:一个换行符。\r:一个回车符。\s:Shell 的名称。\t:24小时制的hours:minutes:seconds格式表示当前时间。\T:12小时制的当前时间。@:12小时制的AM/PM格式表示当前时间。\A:24小时制的hours:minutes表示当前时间。\u:当前用户名。\v:Shell 的版本号。\V:Shell 的版本号和发布号。\w:当前的工作路径。\W:当前目录名。!:当前命令在命令历史中的编号。#:当前 shell 会话中的命令数。$:普通用户显示为$字符,根用户显示为#字符。[:非打印字符序列的开始标志。]:非打印字符序列的结束标志。 ::: 举例来说,[\u@\h \W]$这个提示符定义,显示出来就是[user@host ~]$(具体的显示内容取决于你的系统)。 [root@VM-16-3-centos ~]# echo $PS1
[\u@\h \W]$环境变量 PS2,PS3,PS4除了PS1,Bash 还提供了提示符相关的另外三个环境变量。环境变量PS2是命令行折行输入时系统的提示符,默认为>。 $ echo "hello
world"上面命令中,输入hello以后按下回车键,系统会提示继续输入。这时,第二行显示的提示符就是PS2定义的>。环境变量PS3是使用select命令时,系统输入菜单的提示符。环境变量PS4默认为+。它是使用 Bash 的-x参数执行脚本时,每一行命令在执行前都会先打印出来,并且在行首出现的那个提示符。比如脚本test.sh:#!/bin/bash
echo "hello world"使用-x参数执行这个脚本: $ bash -x test.sh
- echo ‘hello world’
hello world输出的第一行前面有一个+,这就是变量PS4定义的。Shell小妙招调整颜色默认情况下,命令提示符是显示终端预定义的颜色。Bash允许自定义提示符颜色。使用下面的代码,可以设定其后文本的颜色。 :::danger\033[0;30m:黑色\033[1;30m:深灰色\033[0;31m:红色\033[1;31m:浅红色\033[0;32m:绿色\033[1;32m:浅绿色\033[0;33m:棕色\033[1;33m:黄色\033[0;34m:蓝色\033[1;34m:浅蓝色\033[0;35m:粉红\033[1;35m:浅粉色\033[0;36m:青色\033[1;36m:浅青色\033[0;37m:浅灰色\033[1;37m:白色 ::: 如果要将提示符设为红色,可以将PS1设成下面的代码:PS1='[\033[0;31m]<\u@\h \W>$‘但是,上面这样设置以后,用户在提示符后面输入的文本也是红色的。为了解决这个问题, 可以在结尾添加另一个特殊代码\033[00m\033[00m\033[00m,表示将其后的文本恢复到默认颜色。 PS1=’[\033[0;31m]<\u@\h \W>$[\033[00m]‘除了设置前景颜色,Bash 还允许设置背景颜色: :::danger\033[0;40m:蓝色\033[1;44m:黑色\033[0;41m:红色\033[1;45m:粉红\033[0;42m:绿色\033[1;46m:青色\033[0;43m:棕色\033[1;47m:浅灰色 ::: 下面是一个带有红色背景的提示符。 PS1=’[\033[0;41m]<\u@\h \W>$[\033[0m] '生成随机数字游戏image.png#!/bin/bash
function isnumber(){
echo $(expr match “$1” ‘[0-9]’)
}
random=$((RANDOM%100 + 1))
if [[ random%2 -eq 1 ]]; then echo “The $random is odd.”
else echo “The $random is even.”
fi
while [ 1 ]; do
read -p "Please input a number[between 1 and 100]: " number
if [ -z “$number” ]; then echo “Input is NULL, Please input a number!”
else
if [[ $(isnumber $number) -ne 0 ]]; then
if [[ $number -lt $random ]]; then echo “It is too small.”
elif [[ $number -gt $random ]]; then echo “It is too big.”
else echo “You are right.” ; break
fi
else echo “Input is error. Please input a number!”;
fi
fi
done
exit 0生成随机密码对于下面的任何一种方法,你可以通过简单的修改来生成特定长度的密码,或者只使用其输出结果的前N位。希望你正在使用一些类似于LastPass的密码管理器,这样你就不用自己记住这些随机生成的密码了。 使用SHA算法来加密日期这种方法使用SHA算法来加密日期,并输出结果的前32个字符:date +%s | sha256sum | base64 | head -c 32;echo使用内嵌的/dev/urandom这种方法使用内嵌的/dev/urandom,并过滤掉那些日常不怎么使用的字符。这里也只输出结果的前32个字符:< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -cKaTeX parse error: Expected 'EOF', got '#' at position 207: …tr -dc '12345!@#̲%qwertQWERTasdfgASDFGzxcvbZXCVB’ | head -c8; echo ""使用上述某种方法保存为函数randpw。randpw(){ < /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-16};echo;}使用openssl的随机函数这种方法使用openssl的随机函数。如果你的系统也许没有安装openssl,你可以尝试其它九种方法或自己安装openssl。openssl rand -base64 32使用string命令这种方法使用string命令,它从一个文件中输出可打印的字符串:strings /dev/urandom | grep -o ‘[[:alnum:]]’ | head -n 30 | tr -d ‘\n’;echo使用dd命令dd if=/dev/urandom bs=1 count=8 2>/dev/null | base64 -w 0|rev|cut -b 2-|rev
dd if=/dev/urandom bs=1 count=8 | base64 -w 0
#生成1-100随机数
dd if=/dev/urandom bs=1 count=1|od -N 1 |使用md5sumdate | md5sum
#!/bin/bash
产生一个8个字符的随机字符串。
if [ -n “$1” ]
then
str0=“
1
"
e
l
s
e
s
t
r
0
=
"
1" else str0="
1"elsestr0="$”
fi
POS=2
LEN=8
str1=
(
e
c
h
o
"
(echo "
(echo"str0" | md5sum | md5sum) #将字符串通过管道计算两次 md5 来进行两次混淆
randstring=“KaTeX parse error: Expected '}', got 'EOF' at end of input: {str1:POS:$LEN}”
echo “$randstring” #a71224ce
exit $?测试:生成一个8位随机密码生成一个8位的由0-9,A-Z,a-z组成的随机密码,并且至少包含2个数字#!/bin/bash
while true; do
# Random=< /dev/urandom tr -dc A-Z-a-z-0-9 | head -c8
#9BYLmcAa
Random=$(dd if=/dev/urandom bs=1 count=4 2>/dev/null |base64 -w 0|tr -dc A-Z-a-z-0-9 )
DIGITAL=echo $Random | grep -o [0-9] | wc -l
if [[ KaTeX parse error: Expected 'EOF', got '#' at position 32: …; then #̲ Random=`< /dev…(dd if=/dev/urandom bs=1 count=4 2>/dev/null| base64 -w 0|tr -dc A-Z-a-z-0-9 )
else
echo “passwd is $Random” && break
fi
done
exit 0image.png使用数组来完成#!/bin/bash
WORDS=(Q A Z W S X E D C R F V T G B Y H N U J M I K L O P
q a z w s x e d c r f v t g b y h n u j m i k l o p
0 1 2 3 4 5 6 7 8 9)
while [ 1 ]; do
#随机从数组中取8个,拼接成密码
for i in {1…8}
do
index=$((RANDOM % KaTeX parse error: Expected '}', got '#' at position 2: {#̲WORDS[*]})) …{WORDS[index]}
done
if [ $(echo "$password" | grep -o [0-9] | wc -l) -lt 2 ]; then
password=""
else
echo "Passwords is: $password"; break
fi
done
exit 0读取用户信息image.png#!/bin/bash
ADMIN=who | awk '{print $1}'
#打印出第一列,所有的user
for user in
A
D
M
I
N
d
o
H
O
M
E
P
A
T
H
=
‘
m
o
r
e
/
e
t
c
/
p
a
s
s
w
d
∣
g
r
e
p
"
ADMIN do HOME_PATH=`more /etc/passwd | grep "
ADMINdoHOMEPATH=‘more/etc/passwd∣grep"user" | awk -F: ‘{print $6}’ #以:分割,第6列是/home/mantic LASTLOG=
lastlog -u $user | awk ‘NR == 2{print $5,$6,
7
,
7,
7,NF}’` #通过lastlog指令, -u<用户名>:显示指定用户的最近登录信息
echo -e “Hello, $user. Your home path is $HOME_PATH.\nI remember your lastlog is : $LASTLOG.”
done
exit 0配置初始信息#!/bin/bash
name= U S E R h o m e = USER home= USERhome=HOME
echo -e “Greetings, $name. Current time is KaTeX parse error: Expected group as argument to '\"' at end of input: …Your Home is \"home”."分类打包文件#!/bin/bash
for max in 1 10 100 1000
do
if [
m
a
x
=
1
]
;
t
h
e
n
e
c
h
o
‘
f
i
n
d
l
i
n
u
x
−
4.14.292
/
i
n
c
l
u
d
e
/
l
i
n
u
x
−
t
y
p
e
f
−
s
i
z
e
−
max = 1 ]; then echo `find linux-4.14.292/include/linux -type f -size -
max=1];thenecho‘findlinux−4.14.292/include/linux−typef−size−{max}k -exec zip ${max}k.zip {} ;`
elif [
m
a
x
=
1000
]
;
t
h
e
n
e
c
h
o
‘
f
i
n
d
l
i
n
u
x
−
4.14.292
/
i
n
c
l
u
d
e
/
l
i
n
u
x
−
t
y
p
e
f
−
s
i
z
e
+
max = 1000 ]; then echo `find linux-4.14.292/include/linux -type f -size +
max=1000];thenecho‘findlinux−4.14.292/include/linux−typef−size+{min}k -size -
m
a
x
k
−
e
x
e
c
z
i
p
1
m
.
z
i
p
‘
e
l
s
e
m
i
n
=
{max}k -exec zip 1m.zip {} \;` else min=
maxk−execzip1m.zip‘elsemin=((max/10))
echo `find linux-4.14.292/include/linux -type f -size +
m
i
n
k
−
s
i
z
e
−
{min}k -size -
mink−size−{max}k -exec zip ${max}k.zip {} ;`
fi
done
exit 0