Shell经验
# 前言
最近部署的程序放到比较低端的服务器上面去了,有些小功能比较吃性能,而且还是在半夜自动执行的,所以就想着能不能把这些功能放到 shell
脚本里面去,然后再用 crontab
去定时执行,这样就不用每次都去手动执行了,也不用担心性能问题了。结果以为挺简单的玩意也踩了一些坑才搞定😂
# Shell
脚本的运行
假设在命令当前目录下有一个
hello.sh
的脚本文件
- 直接运行,执行
bin/bash test.sh
命令即可 - 给脚本文件添加可执行权限
chmod +x test.sh
然后直接执行命令./test.sh
# Shell
脚本的基本结构
#!/bin/bash
# 说明:这是一个shell脚本
# 作者:xingcxb
# 时间:2021-09-20 08:59:53
# 版本:v1.0.0
echo "Hello World"
2
3
4
5
6
说明:
#!/bin/bash
表示使用bash
解释器来执行脚本,该行为仅仅是一个约定标记,告诉系统使用什么解释器来运行#
表示注释echo
表示输出
# Shell
脚本的基本语法
# Shell
执行时传递参数
如果在执行 shell
时需要对其进行传参,那么可以使用 $n
的形式来获取参数,其中 n
表示第几个参数,$0
表示脚本名称,$1
表示第一个参数,$2
表示第二个参数,以此类推。
#!/bin/bash
echo "不器测试 Shell 传递参数";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
echo "第三个参数为:$3";
2
3
4
5
6
7
假设上面的文件名为 test.sh
当我们执行 ./test.sh 1 2 3
命令时,输出结果如下:
不器测试 Shell 传递参数
执行的文件名:./test.sh
第一个参数为:1
第二个参数为:2
第三个参数为:3
2
3
4
5
# 其它说明
参数处理 | 说明 |
---|---|
$# | 传递到脚本的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数。 如 "$*" 用 「"」 括起来的情况、以 "$1 $2 … $n" 的形式输出所有参数。 |
$$ | 脚本运行的当前进程 ID 号 |
$! | 后台运行的最后一个进程的 ID 号 |
$@ | 与 $* 相同,但是使用时加引号,并在引号中返回每个参数。如 "$@" 用「"」 括起来的情况、以"$1" "$2" … "$n" 的形式输出所有参数。 |
$- | 显示 Shell 使用的当前选项。 |
$? | 显示最后命令的退出状态。0 表示没有错误,其他任何值表明有错误。 |
# Shell
变量
声明变量名需要注意:
- 只包含字母、数字和下划线。变量名可以包含字母(大小写敏感)、数字和下划线 _,不能包含其他特殊字符
- 不能以数字开头: 变量名不能以数字开头,但可以包含数字
- 避免使用
Shell
关键字: 例如if
、then
、else
、fi
、for
、while
等作为变量名,以免引起混淆- 使用大写字母表示常量: 习惯上,常量的变量名通常使用大写字母,例如
PI=3.14
- 避免使用特殊符号: 尽量避免在变量名中使用特殊符号,因为它们可能与
Shell
的语法产生冲突- 避免使用空格: 变量名中不应该包含空格,因为空格通常用于分隔命令和参数
#!/bin/bash
# 声明变量
a = "Hello World"
# 使用变量
echo $a
# 将变量设置为只读
readonly a
# 删除变量
unset a
# 获取字符串长度
echo ${#a}
# 提取子字符串
echo ${a:1:4}
# 查找子字符串
echo `expr index "$a" Wo`
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Shell
数组
Shell
数组用括号来表示,元素用"空格"符号分割开
注意:
- 只支持一维数组(不支持多维数组)
- 数组元素的下标由
0
开始- 可以不使用连续的下标,而且下标的范围没有限制
- 命名规则与变量相同,详情参见 Shell 变量
#!/bin/bash
my_array=(A B "C" D)
# 获取数组中的所有元素,使用 @ 或 * 可以获取数组中的所有元素
echo ${my_array[@]}
echo ${my_array[*]}
# 获取数组中的元素个数,使用 @ 或 * 可以获取数组中的所有元素
echo ${#my_array[@]}
echo ${#my_array[*]}
# 获取数组中的单个元素(指定其下标即可)
echo ${my_array[0]}
2
3
4
5
6
7
8
9
10
# Shell
运算符
- 算数运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
注意:
- 原生
bash
不支持简单的数学运算,但是可以通过其他命令来实现,例如awk
和expr
,expr
最常用- 表达式和运算符之间要有空格,例如
2+2
是不对的,必须写成2 + 2
- 完整的表达式要被
` `
包含,注意这个字符不是常用的单引号,在 Esc 键下边。- 条件表达式要放在方括号之间,并且要有空格,例如:
[$a==$b]
是错误的,必须写成[ $a == $b ]
。- 乘号(
*
)前边必须加反斜杠(\
)才能实现乘法运算if...then...fi
是条件语句- 在
macOS
中shell
的expr
语法是:$((表达式))
,此处表达式中的*
不需要转义符号\
# 关系运算符
假设变量
a
为10
,变量b
为20
:
运算符 | 说明 | 举例 |
---|---|---|
-eq | 检测两个数是否相等,相等返回 true | [ $a -eq $b ] 返回 false |
-ne | 检测两个数是否不相等,不相等返回 true | [ $a -ne $b ] 返回 true |
-gt | 检测左边的数是否大于右边的,如果是,则返回 true | [ $a -gt $b ] 返回 false |
-lt | 检测左边的数是否小于右边的,如果是,则返回 true | [ $a -lt $b ] 返回 true |
-ge | 检测左边的数是否大于等于右边的,如果是,则返回 true | [ $a -ge $b ] 返回 false |
-le | 检测左边的数是否小于等于右边的,如果是,则返回 true | [ $a -le $b ] 返回 true |
# 布尔运算符
假设变量
a
为10
,变量b
为20
:
运算符 | 说明 | 举例 |
---|---|---|
! | 非运算,表达式为 true 则返回 false ,否则返回 true | [ ! false ] 返回 true |
-o | 或运算,有一个表达式为 true 则返回 true | [ $a -lt 20 -o $b -gt 100 ] 返回 true |
-a | 与运算,两个表达式都为 true 才返回 true | [ $a -lt 20 -a $b -gt 100 ] 返回 false |
# 逻辑运算符
假设变量
a
为10
,变量b
为20
:
运算符 | 说明 | 举例 |
---|---|---|
&& | 逻辑的 AND | [[ $a -lt 100 && $b -gt 100 ]] 返回 false |
|| | 逻辑的 OR | [[ $a -lt 100 || $b -gt 100 ]] 返回 true |
# 字符串运算符
假设变量
a
为abc
,变量b
为efg
:
运算符 | 说明 | 举例 |
---|---|---|
= | 检测两个字符串是否相等,相等返回 true | [ $a = $b ] 返回 false |
!= | 检测两个字符串是否不相等,不相等返回 true | [ $a != $b ] 返回 true |
-z | 检测字符串长度是否为 0 ,为 0 返回 true | [ -z $a ] 返回 false |
-n | 检测字符串长度是否不为 0 ,不为 0 返回 true | [ -n "$a" ] 返回 true |
$ | 检测字符串是否不为空,不为空返回 true | [ $a ] 返回 true |
# 文件测试运算符
假设当前目录下有一个
test.sh
文件:
运算符 | 说明 | 举例 |
---|---|---|
-b | 检测文件是否是块设备文件,如果是,则返回 true | [ -b test.sh ] 返回 true |
-c | 检测文件是否是字符设备文件,如果是,则返回 true | [ -c test.sh ] 返回 true |
-d | 检测文件是否是目录,如果是,则返回 true | [ -d test.sh ] 返回 true |
-e | 检测文件是否存在,存在返回 true | [ -e test.sh ] 返回 true |
-f | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true | [ -f test.sh ] 返回 true |
-g | 检测文件是否设置了 SGID 位,如果是,则返回 true | [ -g test.sh ] 返回 true |
-k | 检测文件是否设置了粘着位(Sticky Bit ),如果是,则返回 true | [ -k test.sh ] 返回 true |
-p | 检测文件是否是有名管道,如果是,则返回 true | [ -p test.sh ] 返回 true |
-u | 检测文件是否设置了 SUID 位,如果是,则返回 true | [ -u test.sh ] 返回 true |
-r | 检测文件是否可读,可读返回 true | [ -r test.sh ] 返回 true |
-w | 检测文件是否可写,可写返回 true | [ -w test.sh ] 返回 true |
-x | 检测文件是否可执行,可执行返回 true | [ -x test.sh ] 返回 true |
-s | 检测文件是否为空(文件大小是否大于 0 ),不为空返回 true | [ -s test.sh ] 返回 true |
其它:
-S
: 判断某文件是否socket
-L
: 检测文件是否存在并且是一个符号链接。
# Shell
echo
命令
Shell
的 echo
指令是用于字符串的输出
# 显示普通字符串
#!/bin/bash
echo "It is a test"
echo It is a test
2
3
# 显示转义字符
#!/bin/bash
echo "\"It is a test\""
2
# 显示变量
#!/bin/bash
# 读取键盘输入
read name
echo "$name It is a test"
2
3
4
# 显示换行
#!/bin/bash
echo -e "OK! \n" # -e 开启转义
echo "It is a test"
2
3
# 显示不换行
#!/bin/bash
echo -e "OK! \c" # -e 开启转义 \c 不换行
echo "It is a test"
2
3
# 显示结果定向至文件
#!/bin/bash
echo "It is a test" > myfile
2
# 原样输出字符串,不进行转义或取变量(用单引号)
#!/bin/bash
echo '$name\"'
2
# 显示命令执行结果
注意:下面的命令使用的是反引号
`
,而不是单引号'
#!/bin/bash
echo `date`
2
# Shell
流程控制
# if
语句
#!/bin/bash
# if 语句
if condition
then
command1
command2
...
commandN
fi
# if else 语句
if condition
then
command1
command2
...
commandN
else
command
fi
# if else-if else 语句
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
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
31
# for
循环
#!/bin/bash
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
2
3
4
5
6
7
8
# while
循环
#!/bin/bash
while condition
do
command
done
2
3
4
5
# until
循环
#!/bin/bash
until condition
do
command
done
2
3
4
5
# case
语句
#!/bin/bash
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 跳出循环
break
命令:跳出所有循环continue
命令:跳出当前循环return
命令:从函数中跳出exit
命令:退出脚本exit n
命令:退出脚本并返回n
值,n
的范围为0-255
,0
表示成功,其他值表示失败
# Shell
函数
注意:
- 可以带
function fun()
定义,也可以直接fun()
定义,不带任何参数- 参数返回,可以显示加:
return
返回,如果不加,将以最后一条命令运行结果,作为返回值。return
后跟数值n
(0-255]
#!/bin/bash
# 函数定义
demoFun(){
echo "这是我的第一个 shell 函数!"
}
# 函数调用
echo "-----函数开始执行-----"
demoFun
echo "-----函数执行完毕-----"
2
3
4
5
6
7
8
9
# 函数参数
在 Shell
中,调用函数时可以向其传递参数。在函数体内部,通过 $n
的形式来获取参数的值,例如,$1
表示第一个参数,$2
表示第二个参数...
注意:
$10
不能获取第十个参数,获取第十个参数需要${10}
。当n>=10
时,需要使用${n}
来获取参数
#!/bin/bash
# 函数定义
funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
# 函数调用
funWithParam 1 2 3 4 5 6 7 8 9 34 73
2
3
4
5
6
7
8
9
10
11
12
13
# 函数返回值
Shell
函数返回值只能是整数,一般用来表示函数执行成功与否,0
代表成功,其他值表示失败。如果 return
其他数据,比如一个字符串,往往会得到错误提示:“numeric argument required”
。
如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值。
#!/bin/bash
# 函数定义
funWithReturn(){
echo "这个函数会对输入的两个数字进行相加运算..."
echo "输入第一个数字: "
read aNum
echo "输入第二个数字: "
read anotherNum
echo "两个数字分别为 $aNum 和 $anotherNum !"
return $(($aNum+$anotherNum))
}
# 函数调用
funWithReturn
# 函数返回值在调用该函数后通过 $? 来获得
echo "输入的两个数字之和为 $? !"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Shell
输入 /
输出重定向
| 运算符 | 说明 |
| command > file
| 将输出重定向到 file
|
| command < file
| 将输入重定向到 file
|
| command >> file
| 将输出以追加的方式重定向到 file
|
| n > file
| 将文件描述符为 n
的文件重定向到 file
|
| n >> file
| 将文件描述符为 n
的文件以追加的方式重定向到 file
|
| n >& m
| 将输出文件 m
和 n
合并 |
| n <& m
| 将输入文件 m
和 n
合并 |
| << tag
| 将开始标记 tag
和结束标记 tag
之间的内容作为输入 |
注意: 一般情况下,每个
Unix/Linux
命令运行时都会打开三个文件:
- 标准输入文件(
stdin
):stdin
的文件描述符为0
,Unix
程序默认从stdin
读取数据- 标准输出文件(
stdout
):stdout
的文件描述符为1
,Unix
程序默认向stdout
输出数据- 标准错误文件(
stderr
):stderr
的文件描述符为2
,Unix
程序会向stderr
流中写入错误信息
# 输出重定向
#!/bin/bash
# 输出重定向到文件
echo "不器小窝:xingcxb.com" > myfile
# 输出重定向到文件(追加)
echo "不器小窝:xingcxb.com" >> myfile
# 命令的输出结果重定向到文件
echo `date` > myfile
2
3
4
5
6
7
# 输入重定向
#!/bin/bash
# 输入重定向
wc -l < myfile
# 输入重定向(追加)
wc -l >> myfile
2
3
4
5
# Here Document
Here Document
是 Shell
中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell
脚本或程序
基本语法:
command << delimiter
document
delimiter
2
3
注意:
- 结尾的
delimiter
一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和tab
缩进- 开始的
delimiter
前后的空格会被忽略掉
# /dev/null
文件
/dev/null
文件,它也称作空设备,我们可以将不需要的输出重定向到 /dev/null
文件,例如:
#!/bin/bash
# 将 stdout 和 stderr 合并后重定向到 /dev/null
echo "不器小窝:xingcxb.com" > /dev/null 2>&1
# 将 stdout 和 stderr 合并后重定向到 /dev/null(追加)
echo "不器小窝:xingcxb.com" >> /dev/null 2>&1
2
3
4
5
如果希望某个比较大的日志文件快速清空的话可以考虑采用
myfile > /dev/null
的方式,这样的话就不会占用磁盘空间了
# Shell
文件包含(执行外部 Shell
文件)
#!/bin/bash
# 引入外部文件
. ./test.sh
# 或者
source ./test.sh
2
3
4
5
实战:
注意:
test1.sh
和test2.sh
在同一个目录下test1.sh
如果是被引用执行的可以不需要执行权限,如果是直接执行的需要执行权限
test1.sh
文件内容如下:
#!/bin/bash
url="https://xingcxb.com"
2
3
test2.sh
文件内容如下:
#!/bin/bash
. ./test1.sh
echo $url
# 或者
source ./test1.sh
echo $url
2
3
4
5
6
7