跳转至

Shell 脚本基础

约 1548 个字 237 行代码 9 张图片 预计阅读时间 8 分钟

Info

本章的 Shell 脚本,如无特别说明,均指 Bash。

Shell 脚本的作用

  • 把多条命令放在一起执行,执行一次可以执行多条命令
  • 添加了数组、循环、条件、逻辑判断等功能,能够执行复杂的操作

运行脚本

直接执行 Shell 脚本

前提:用户对脚本有 rx 权限。

一般可直接输入脚本路径,默认会使用当前 Shell 执行(之后说具体情况):

脚本

如果脚本在当前目录下,务必加 ./,不能直接写文件名:

./脚本文件名

也适用于其他的可执行程序。

指定 Shell 执行 Shell 脚本

不需要脚本有 x 权限,但至少有 r 权限。

Shell命令 脚本

如果脚本在当前目录,不需要再加 ./,如:

bash test.sh

source.

不需要脚本有 x 权限,但至少有 r 权限。

source 脚本
. 脚本

与其他方式的区别:其中定义的变量在此次 bash 进程中会保留下来。

注释

一般的注释

# 开头,在一行内生效。

也可以将命令屏蔽掉。

1
2
3
# 这是注释
ls -al  # 列出当前目录下所有文件的详细信息
# rm -rf /

特殊的注释:shebang

形如:

#!/bin/bash

放在第一行,必须写绝对路径。

用来告诉 Shell ,脚本由谁来执行,不一定是 Shell;写了之后可以直接执行:

#!/bin/cat
Hello world

但不是所有命令都会忽略注释,包括 shebang,如上例的执行结果:

#!/bin/cat
Hello world

env 搭配使用

上例考虑到 bash 可能不在 /bin 下,比较保险的写法:

#!/usr/bin/env bash

如果要使用的命令带选项,应该给 env 加上 -S 选项:

#!/usr/bin/env -S bash -r

输入、计算

read - 输入

read [选项] [变量名]

变量名本身不用加 $,下同。

如不指定变量名,则会放在 REPLY 变量中。

选项:

  • -p 文字:提示文本
  • -t 整数:等待的秒数,如等待期间无输入,则跳过,变量名会赋为空值,返回值非 0

直接计算

仅支持整数四则运算、取余、括号(+ - * / % ( )),除法仅取整数部分。

可使用如下格式:

$((算式))
# 等于算式结果

例:

1
2
3
4
5
6
7
8
9
ding@ding-server:~$ echo 1+2
1+2
ding@ding-server:~$ echo (1+2)
-bash: syntax error near unexpected token `1+2'
ding@ding-server:~$ echo $(1+2)
1+2: command not found

ding@ding-server:~$ echo $((1+2))
3

bc - 简单的计算器

bc [选项] [文件]
... | bc [选项]

不指定文件,则进入交互模式。

选项:

  • -l:加载数学库,支持更多命令

支持的命令

Info

只列举一部分,详细的可参考手册。

  • 整数与小数的四则运算、取余、括号:+ - * / % ( )
  • a ^ b # b 为整数
    
  • 变量定义
    a = 1
    
  • 特殊变量
    1
    2
    3
    scale   # 保留小数位数,默认为 0,加减乘余不受限
    ibase # 输入进制,默认为 10
    obase # 输出进制,默认为 10
    
  • 自操作
    1
    2
    3
    4
    5
    a 操作符= b  # a = a 操作符 b
    a ++  # 输出 a,之后 a += 1
    a --  # 输出 a,之后 a -= 1
    ++ a  # a += 1,之后输出 a
    -- a  # a -= 1,之后输出 a
    
  • 判断大小:为真为 1 ,否则为 0
    1
    2
    3
    4
    5
    6
    a < b
    a > b
    a == b
    a != b
    a <= b
    a >= b
    
  • 逻辑运算:返回 01
    1
    2
    3
    a && b
    a || b
    !a
    
  • 常用方法
    1
    2
    3
    length(语句)    # 返回语句表示数字的总位数
    scale(语句) # 返回语句表示数字的小数位数
    sqrt(语句)  # 算术平方根,只能用在非负数
    
  • 添加 -l 选项后支持的一些函数
    1
    2
    3
    4
    5
    6
    # 默认 scale = 20
    s(x)  # sin x,x 为弧度制
    c(x)  # cos x,x 为弧度制
    a(x)  # arctan x,返回弧度值
    l(x)  # ln x
    e(x)  # e^x
    

求 π 值:

ding@ding-server:~$ echo "scale=20; 4*a(1)" | bc -l
3.14159265358979323844

判断式

test - 判断

直接执行无显示,需看返回值:0 为真,1 为假。

1
2
3
4
5
6
# test 后为条件
test 选项 文件
test 文件1 选项 文件2
test 整数1 选项 整数2
test 选项 字符串
test 字符串1 选项 字符串2

选项

单个文件判断
  • 文件类型判断

    • -e:是否存在
    • -s:是否存在且非空
    • 是否存在且为某类型的文件
    -b -c -d -p -f -L / -h -S
    块设备 字符设备 目录 管道 普通文件 符号链接 套接字
  • 文件权限检测:是否存在且有对应权限 | -r | -w | -x | -u | -g | -k | | ------ | ------ | ------ | ------ | ------ | ------ | | 读 | 写 | 执行 | SUID | SGID | SBIT |

两个文件比较

  • -nt:(newer than)前面的文件是否比后面的文件新
  • -ot:(older than)前面的文件是否比后面的文件旧
  • -ef:两个文件是否是一个文件(看是否指向同一个 inode)

两个整数判定

选项 全称 含义
-eq equal ==
-ne not equal !=
-gt greater than >
-lt less than <
-ge greater than or equal >=
-le less than or equal <=

字符串判定

  • 单个字符串
    • -z:字符串长度是否为 0(是否为空字符串),若空为 0 (真)
    • -n 或省略:字符串长度是否非 0(是否非空字符串) ,若空为 1(假)
  • 多个字符串
    • ==
    • !=

多重条件

  • -a:与
    test 条件1 -a 条件2
    
  • -o:或
    test 条件1 -o 条件2
    
  • !:非
    test ! 条件
    

简写

[ 条件 ]

注意条件两端以及条件内的判断符两边都要有空格;变量最好用双引号包裹,以免出现其中带空格的情况。

[ "$HOME" == "$MAIL" ]

流程结构

条件结构

if

Shell 中 0 为真,非 0 为假。

1
2
3
4
if 条件
then
    条件为真的语句
fi

不带 else 的 if 语句的流程图
不带 else 的 if 语句的流程图

1
2
3
4
5
6
if 条件
then
    条件为真的语句
else
    条件为假的语句
fi

带 else 的 if 语句的流程图
带 else 的 if 语句的流程图

1
2
3
4
5
6
7
8
9
if 条件1
then
    条件 1 为真的语句
elif 条件2
then
    条件 1 为假,条件 2 为真的语句
else
    上述条件均为假的语句
fi

elif 块可以写多个。

带 elif 的 if 语句的流程图
带 elif 的 if 语句的流程图

case / switch

case $变量名 in
  值1)
    变量值为值 1 时的语句,即语句 1
    ;;
  值2)
    变量值为值 2 时的语句,即语句 2
    ;;
  *)
    以上条件都不满足时的语句,即默认语句
    ;;
esac

值1) 块可以写多个。

case 语句的流程图
case 语句的流程图

循环结构

while

条件成立时进行循环,直到不成立为止。

1
2
3
4
while 条件
do
    循环体
done

while 语句的流程图
while 语句的流程图

until

进行循环,直到条件成立为止。

1
2
3
4
until 条件
do
    循环体
done

until 语句的流程图
until 语句的流程图

for

使用序列
1
2
3
4
for 变量名 in 值1 值2 值3
do
    循环体
done

给变量名分别赋不同值进行循环。

上面的 值1 值2 值3 被称为序列。序列一般可以用空格或换行符分割,但后者一般只用在变量中。

使用序列的 for 语句的流程图
使用序列的 for 语句的流程图

序列的生成

想要生成 1~100 的序列,可以用:

1
2
3
seq 1 100
# 或
{1..100}

用在 for 中如下:

1
2
3
4
for 变量名 in $(seq 1 100)
do
    循环体
done
1
2
3
4
for 变量名 in {1..100} 
do
    循环体
done

后者的形式也可以用于字母:

{a..g}
不使用序列
1
2
3
4
for (( 循环前操作; 条件; 每轮循环后操作 ))
do
    循环体
done

一般会在循环前赋值,条件和循环后操作都会操作值。

不使用序列的 for 语句的流程图
不使用序列的 for 语句的流程图

函数、返回值与参数

函数定义与调用

一般的定义方式:

1
2
3
function 函数名() {
    代码段
}

最简单的定义方式:

1
2
3
函数名 {
    代码段
}

函数可以给返回值,写在代码段中:

return 要返回的值

一般的调用方式:

函数名

带参数的调用方式:

函数名 参数1 参数2 ...

取函数执行完后的返回值:

$?

函数的基本结构
函数的基本结构

函数的参数

  • $#:参数个数
  • $*:作为字符串输出所有参数,分隔符默认为空格
  • $@:作为数组输出所有参数(可用作序列)
  • ${数字}:第几个参数(从 1 开始,10 以下可以不用大括号)

例:

函数名 参数1 参数2 参数3
1
2
3
4
5
6
$#=3
$*="参数1 参数2 参数3"
$@="参数1" "参数2" "参数3"
$1="参数1"
$2="参数2"
$3="参数3"

shift - 参数变量号偏移

shift

例:

函数名 参数1 参数2 参数3
1
2
3
4
5
6
$#=3
$*="参数1 参数2 参数3"
$@="参数1" "参数2" "参数3"
$1="参数1"
$2="参数2"
$3="参数3"

函数体中使用 shift 语句之后:

1
2
3
4
5
$#=2
$*="参数2 参数3"
$@="参数2" "参数3"
$1="参数2"
$2="参数3"

Shell 脚本的默认变量

命令路径 选项1 选项2 选项3
1
2
3
4
5
6
7
$0="命令路径"
$1="选项1"
$2="选项2"
$3="选项3"
$#=3
$@="选项1" "选项2" "选项3"
$*="选项1 选项2 选项3"

也适用偏移。

脚本的返回值

exit 返回值

辅助功能与补充

Shell 的选项

bash [选项] [脚本文件]

选项:

  • -n:不执行脚本,仅查询语法问题
  • -v:执行脚本前,输出脚本内容
  • -x:将使用到的脚本内容先显示在屏幕上

例:

t.sh 内容如下:

1
2
3
4
5
6
#!/bin/bash
echo $0
for i in $@ 
do
        echo $i
done
ding@ding-server:~$ bash -v ./t.sh a c d f e
#!/bin/bash
echo $0
./t.sh
for i in $@ 
do
        echo $i
done
a
c
d
f
e

ding@ding-server:~$ bash -x ./t.sh a c d f e
+ echo ./t.sh
./t.sh
+ for i in $@
+ echo a
a
+ for i in $@
+ echo c
c
+ for i in $@
+ echo d
d
+ for i in $@
+ echo f
f
+ for i in $@
+ echo e
e

执行 Shell 脚本时传入变量

变量名=变量值 bash [选项] [脚本文件]

在 Shell 脚本内包含其他 Shell 脚本

利用 source.

随机数

环境变量 RANDOM 为整数,取值范围 0~32767

将其转化为较小的 0~n 区间的一种方式:

$(($RANDOM % (n+1)))

日期时间

ding@ding-server:~$ date                    # 显示当前日期时间
Mon 30 May 2022 06:24:22 PM CST
ding@ding-server:~$ date +%Y-%m-%d          # 格式化输出日期
2022-05-30
ding@ding-server:~$ date +%H:%M:%S          # 格式化输出时间
18:24:52
ding@ding-server:~$ date +"%Y-%m-%d %H:%M:%S"       # 格式化输出日期时间
2022-05-30 18:31:35
ding@ding-server:~$ date +%s                # 日期时间转时间戳
1653906461
ding@ding-server:~$ date --date='@2147483647'       # 时间戳转日期时间
Tue 19 Jan 2038 11:14:07 AM CST
ding@ding-server:~$ date --date='2022-01-01 10:00:00'   # 日期时间输入
Sat 01 Jan 2022 10:00:00 AM CST
ding@ding-server:~$ date --date='2022-01-01 10:00:00 UTC'   # 日期时间时区输入
Sat 01 Jan 2022 06:00:00 PM CST

获取路径的目录部分和文件部分

该命令不检查文件是否存在,所以也可以作为分割字符串的方式。

ding@ding-server:~$ dirname /dir1/dir2/file1
/dir1/dir2
ding@ding-server:~$ basename /dir1/dir2/file1
file1
ding@ding-server:~$ dirname dir1/dir2/file1
dir1/dir2
ding@ding-server:~$ basename dir1/dir2/file1
file1
ding@ding-server:~$ dirname file1
.
ding@ding-server:~$ basename file1
file1

参考资料