跳转至

Shell 脚本编程

1. Shell 简介

1.1. Shell 环境

  1. 运维工作必备技能

  2. 种类众多,这里主要采用 Bash,也就是 Bourne Again Shell

1.2. 第一个shell脚本

hello_world.sh

#!/bin/bash

# 输出
echo "Hello, bash world!"
- #!/bin/bash:告诉系统其后路径所指定的程序 /bin/bash, 即是解释此脚本文件的 Shell 程序 - 文件扩展名为 sh(代表 shell) - 这里的扩展名并不影响脚本执行,见名知意就好 - 例如:若用 php 编写 Shell脚本,那么用 php 就好。

2. Shell 基础

2.1. Shell 执行方式

1、作为可执行程序

# 使脚本具有执行权限
chmod +x ./hello_world.sh
# 执行脚本
./hello_world.sh

2、作为解释器参数

/bin/sh test.sh

2.2. Shell 注释

#!/bin/bash

# 这里是单行注释

:<<a
这里是多行注释:第一行
这里是多行注释:第二行
这里是多行注释:第三行
a

2.3. 开发规范

  1. 脚本命名
  2. 首行必须写脚本解释器
  3. 编写脚本基本信息

    • 文件中尽量使用英文注释,防止本机或切换系统环境后中文乱码
    • 常见的注释信息:名称,功能描述,版本,作者,日期
  4. 优秀书写习惯

    1. 成对编写
    2. 空格留白
  5. 编写脚本时,务必记住:脚本执行从上到下,依次执行。

  6. 代码书写优秀习惯:
    • 成对书写内容,防止遗漏。如:()/{}/[]/''/""
    • [] 中括号两端都要有空格。即书写时便预留空格,再退格书写内容。
    • 流程控制语句一次性书写完,再后退添加内容。
    • 通过缩进让代码易读
      • 实际上,缩进没有任何语法意义

3. 变量

3.1. 变量的分类

变量主要分为三类:

  1. 本地变量
    • 普通变量
    • 命令变量
  2. 全局变量

  3. Shell 内置变量

3.2. 变量的定义

3.2.1. 普通变量

1, 显式赋值

#!/bin/bash

# 方式一:
# 变量值必须是一个整体,中间没有特殊字符
my_str1=/user

# 方式二:
my_str2='/user'
# 可以输出空格等特殊字符,但是,若变量值有可解析变量,那么会原值输出
my_str2='dan $my_str1'

# 方式三:
my_str3="/user"
# 可输出空格等特殊字符,同时,如果变量值范围内有可解析变量,那么解析后再输出
my_str3="shuang $my_str1"
- 变量名和等号之间不能有空格 - 变量名的命名须遵循如下规则: - 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头 - 中间不能有空格,可以使用下划线 _ - 不能使用标点符号 - 不能使用bash里的关键字(可用help命令查看保留关键字)

2, 语句赋值

#!/bin/bash

# 将 /etc 目录下的文件名循环导出
for list in $(ls /etc)

3.2.2. 命令变量

显式赋值

#!/bin/bash

# 方式一:
# 采用反引号引用 Linux 系统命令
dir=`pwd`

# 方式二:
# 利用 $ + 小括号
dir=$(pwd)
- 命令变量的执行流程为:先执行系统命令,再赋值。

3.2.3. 全局变量

查看全局变量值

# 查看当前系统所有全局变量命令
env

# 精确查看某全局变量
env | grep SHELL

# 当然,也可以直接查看变量值
$SHELL

定义全局变量

#!/bin/bash

# 方式一:先赋值变量,再使用 export 定义全局变量
aar='/home/aaron'
export aar

# 方式二:export 变量=值
export aar='/home/aaron'

3.3. 变量的使用

使用一个定义过的变量,只要 在变量名前面加美元符号 即可 - 填加 **花括号 ** 可以帮助解释器识别变量的边界

3.3.1. 查看变量

#!/bin/bash

# 方式一:
$var_name

# 方式二:
"$var_name"

# 方式三:使用频率较高
${var_name}

# 常见使用场景
echo "Here is a commond using other var ${var_name}"

# 方式四:标准使用方式
"${var_name}"

3.4. 取消变量

#!/bin/bash

# 取消变量
uset var_name

3.5. Shell 内置变量

3.5.1. 脚本相关

变量 说明
s0 获取当前执行的she11脚本文件名,包括脚本路径
sn 获取当前执行的she11脚本的第n个参数值,n=1..9,当n为0时表示脚本的文件名,如果n大于9就要用大括号括起来{10}
s# 获取当前she11命令行中参数的总个数
s? 获取执行上一个指令的返回值(0为成功,非0为失败)

验证

#!/bin/bash

# 验证与查看:获取脚本文件名命令:$0
echo "我的脚本名称:$0"

# 验证与查看:获取命令行参数命令 $n

echo "命令行第一个参数为:$1"
echo "命令行第二个参数为:$2"
echo "命令行第三个参数为:$3"
echo "命令行第零个参数为:$0"

# 验证与查看:获取当前脚本传入参数的数量命令:$#
echo "当前脚本传入参数数量为:$#"

# 验证与查看:获取执行上一个指令的返回值命令:$?
echo "上一条指令是否执行成功:$?"
- $?:仅仅查看执行上一个指令的状态:成功 or 失败。

4. Shell 进阶

4.1. 测试语句

测试语句格式: 1. test 条件表达式 2. [ 条件表达式 ] - 这里的条件表达式前后都需要 空格 隔开

测试语句状态: - 条件成立,返回状态值:0 - 不成立,返回状态值:1

4.2. 条件表达式

4.2.1. 逻辑表达式

1, 逻辑表达式 &&

命令1 && 命令2 - 命令1执行成功,才执行命令2 - 执行不成功,命令2也不执行。

2,逻辑表达式 ||

命令|| 命令2 - 命令1执行成功,命令2不再执行 - 命令1执行不成功,命令2再执行

4.2.2. 文件表达式

命令 说明
-f 判断输入内容是否是一个文件
-d 判断输入内容是否是一个目录
-x 判断输入内容是否可执行

4.2.3. 数值操作符

比较两个数之间的关系

表达式 关系
n1 -eq n2 等于
n1 -gt n2 大于
n1 -lt n2 小于
n1 -ne n2 不等于

4.2.4. 字符串比较

表达式 关系
str1 == str2 str1 与 str2 内容一致
str1 != str2 str1 与 str2 内容不一致

4.2.5. 计算表达式

格式 表达式 说明
格式1 $((计算表达式)) 只能用 + - * /() 等操作符
格式2 let 计算表达式

4.3. 脚本常见 Linux 符号

4.3.1. 重定向符号:> & >>

符号 说明
> 将符号 左测 内容,以 覆盖 方式输入到 右侧 文件中
>> 将符号 左测 内容,以 追加 方式输入到 右侧 文件末尾中

4.3.2. 管道符 |

符号 说明
命令1 | 命令2 将符号 左测 命令1执行结果,传递给管道符 右侧 命令2中

4.3.3. 后台展示符号 &

符号 说明
命令 & 将一个命令从前台转到后台执行
# 终端前台休眠 10s
sleep 10

# 转入后台执行
sleep 10 &
# 查看后台进程
ps aux | grep sleep

4.3.4. 全部信息符号 2>&1

符号说明: - 1:表示正确输出的信息 - 2:表示错误输出的信息 - 2>&1:代表所有输出的信息

测试说明: 1. 添加文件 all_info.sh

#!/bin/bash

# 测试:全部信息符号 2>&1

echo "这一条是正确命令"
echo "下一条是错误命令"
sdflakj

  1. 执行测试命令
# 命令1
$ bash all_info.sh 1>> right_info 2>> err_info

# 命令2
$ bash all_info.sh >> all_info 2>&1

Linux 系统垃圾桶 /dev/null - /dev/null 是Linux 下的一个设备文件 - 该文件类似一个垃圾桶,容量无限大,也无需删除 - 因此,可以利用此特点执行测试命令

# 常用命令
$ bash bash_files.sh >> /dev/null 2>&1

4.4. 脚本常见 Linux 命令

4.4.1. 文本搜索命令 grep

命令格式

# 命令格式:
$ grep [参数] [关键字] <file_name>`

# 与管道结合时,不用添加<file_name>
$ ls | grep file_name

# 查看帮助
$ grep --help
- -c:只输出匹配行的计数。 - -n:显示匹配行及行号。 - -v:显示不包含匹配文本的所有行。

应用实例 1,编写文件 find.txt

Hello world, aar
Hello world, bbr
Hello world, ccr
Hello world, aar

2,参数应用

# 查找
$ grep aar find.txt
Hello world, aar
Hello world, aar

$ grep -c aar find.txt
2

# 显示匹配内容行号
grep -n aar find.txt

# 显示不匹配内容
grep -v aar find.txt

小技巧

# 精确定位错误代码:
# 将会显示<file_name><行号><错误关键字行内容>
$ grep -nr [错误关键字] *

4.4.2. 行文件编辑工具 sed

sed 命令以行为单位进行文件编辑

命令格式

# 命令格式
$ sed [参数] '<匹配条件>[动作]' [文件名]

# 查看帮助
$ sed --help

参数含义: - 参数为空:表示 sed 操作效果不对文件进行编辑。 - -i:表示对文件进行编辑 - MAC 版本的bash中使用 -i 参数,必须在后面单独加参数:-i ''

匹配条件: 匹配条件分为两种:数字行号或者关键字匹配

  1. 数字行号:关于数字行号的匹配内容分三类

    • 行号
    • 列号
    • 全体
  2. 关键字匹配的格式:'/关键字/'

    • 这里的隔离符号 / 可以更换成 、#、! 等符号
    • 根据情况使用,如果关键字和隔离符号有冲突,就更换成其他的符号即可。

匹配动作 这里的匹配动作都在参数 -i 的条件下生效。同时又分为各自不同的命令。 - a:在匹配到的内容下一行增加内容 - i:在匹配到的内容上一行增加内容 - d:删除匹配到的内容 - s:替换匹配到的内容

  1. 替换命令 s

  2. 命令格式:sed -i [替换格式] [文件名]

  3. 替换格式的写法:
    • '行号s###列号' -——>'行号s#原内容##列号'——>'行号s#原内容#替换后内容#列号'`
    • 行号: 为替换文件的 第几行, 默认不写为 所有行
    • 列号: 为一行中匹配到的 第几个 匹配内容,另外,有参数 g 表示全部匹配。
# 匹配首行内容的首个内容
$ sed -i '1s#原内容#替换后内容#' seed.txt

# 匹配第二行内容的首个内容
$ sed -i '2s#原内容#替换后内容#' seed.txt

# 匹配所有行内容的首个内容
$ sed -i 's#原内容#替换后内容#' seed.txt

# 匹配第一行的第二个内容
$ sed -i '1s#原内容#替换后内容#2' seed.txt

# 匹配所有行的第二个内容
$ sed -i 's#原内容#替换后内容#2' seed.txt

# 匹配所有行,查找到得全部匹配
$ sed -i 's#原内容#替换后内容#g' seed.txt

# 指定行号和列号匹配内容
$ sed -i '行号s#原内容#替换后内容#列号' seed.txt
  1. 增加到下一行命令 a
  2. 作用:在指定行号的 下一行 增加内容
  3. 格式:sed -i '行号a\增加的内容' <文件名>

    • 如果增加多行,可以在行号位置写个范围值,彼此间使用 逗号 隔开,例如: sed -i '1,3a\增加内容' <文件名>
  4. 增加到当行命令 i

  5. 作用:在指定行号的 当行 增加内容
  6. 格式:sed -i '行号i\增加的内容' <文件名>

    • 如果增加多行,可以在行号位置写个范围值,彼此间使用 逗号 隔开,例如: sed -i '1,3i\增加内容' <文件名>
  7. 删除命令 d

  8. 作用:指定行号删除
  9. 格式:sed -i '行号d' <文件名>
    • 如果删除多行,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如:sed -i '1,3d' <文件名>

应用实例 1,编写文件 seed.txt

Hello seed, seed, seed of aar
Hello seed, seed, seed of bbr
Hello seed, seed, seed of ccr
Hello seed, seed, seed of aar

2,参数应用


4.4.3. 文档编辑工具 awk

awk是一个功能非常强大的文档编辑工具,它不仅能以行为单位还能以列为单位处理文件。

命令格式

# 命令格式
$ awk [参数] '[动作]' [文件名]

常见参数: - -F:指定行的分隔符

常见动作: - print:显示两容 - $0:显示当前行所有内容 - sn:显示当前行的第n列内容,如果存在多个sn,它们之间使用逗号(,)隔开

常见内置变量 - FILENAME:当前输入文件的文件名,该变量是只读的NR指定显示行的行号 - NE:输出最后一列的内容 - OFS:输出格式的列分隔符,缺省是空格 - FS:输入文件的列分融符,缺省是连续的空格和Tab

应用实例 1,编写文件 - awk.txt

Hello awk awk awk of aar
Hello awk awk awk of bbr
Hello awk awk awk of ccr
Hello awk awk awk of aar

  • awk2.txt
    Hello:awk:awk:awk:of:aar
    Hello:awk:awk:awk:of:bbr
    Hello:awk:awk:awk:of:ccr
    Hello:awk:awk:awk:of:aar
    

2,参数应用

# 查看文本内容
cat awk.txt

# 显示第一、三行内容
$ awk '{print $1, $3}' awk.txt

# 显示
$ awk '{print NR, $1, $3}' awk.txt

# 设置文本分隔符显示内容
$ awk -F ':' '{print $2, $4}' awk2.txt

# 设置显示分隔符显示内容
$ awk -F ':' 'BEGIN{OFS=''} {print NR, $0}' awk.txt

4.4.4. 查找命令 find

命令格式

# 命令格式
$ find [路径] [参数] [关键字]

# 查看帮助
$ find --help

参数详解 - -name:按照文件名查找文件。 - -perm:按照文件权限来查找文件。 - -user:按照文件属主来查找文件。 - -group:按照文件所属的组来查找文件。 - -type:查找某一类型的文件,诸如: - b:块设备文件 - d:目录 - c:字符设备文件 - p:管道文件 - l:符号链接文件 - f:普通文件 - -size n[c]:查找文件长度为n块的文件,带有 c 表示文件长度以字节计。 - -depth:在查找文件时,首先查找当前目录中的文件,然后再在其子目录中查找。 - -mindepth n:在查找文件时,查找当前目录中的第n层目录的文件,然后再在其子目录中查找。 - !:表示取反

应用实例 1,编写文件 awk.txt

Hello awk, awk, awk of aar
Hello awk, awk, awk of bbr
Hello awk, awk, awk of ccr
Hello awk, awk, awk of aar

2,参数应用

# 查看文件 file_name
$ find /root -name "file_name"

# 查看目录
$ find /root -type d

5. Shell 流程控制

5.1. 简单流程控制

5.1.1. 单分支 if 语句

语法格式

#!/bin/bash

if [ 条件 ]
then
    指令
fi

实例

#!/bin/bash

# 验证单分支 if 语句

if [ "$1" == "nan" ]
then
    echo "本次脚本执行未传递参数!"
fi

5.1.2. 双分支 if 语句

语法格式

#!/bin/bash

if []
then
    指令1
else
    指令2
fi

5.1.3. 多分支 if 语句

语法格式

#!/bin/bash

if [ 条件 ]
then
    指令1
elif [ 条件2 ]
then
    指令2
else
    指令3
fi

案例:脚本执行参数

需求: - 要求脚本执行需要有参数,通过传入参数来实现不同的功能 - 参数和功能详情如下:

参数 执行效果
start 服务启动中..
stop 服务关闭中..
restart 服务重启中..
* 脚本x.sh使用方式 x.sh [start|stop|restart]

编写:

#!/bin/bash

a="$1"

if [ "${a}" == "start" ]
then
    echo "服务启动中..."
elif [ "${a}" == "stop" ]
then
    echo "服务关闭中..."
elif [ "${a}" == "restart" ]
then
    echo "服务重启中..."
else
    echo "脚本 $0 的使用方式: $0 [ start|stop|restart ]"
fi

5.1.4. case 分支语句

语法格式

#!/bin/bash

case 变量名 in
    值1)
        指令1
        ;;
    值2)
        指令2
        ;;
    值3)
        指令3
        ;;
    esac
- 首行关键字是 case,末行关键字 esac - 每个选择项后面都有 ) - 每个选择执行语句结尾都有两个分号 ;;

改写:脚本执行参数

#!/bin/bash

a=$1

case "${a}" in
    "start")
        echo "服务启动中.."
        ;;
    "stop")
        echo "服务关闭中.."
        ;;
    "restart")
        echo "服务重启中.."
        ;;
    "*")
        echo "脚本 $0 使用方式: $0 [start|stop|restart]"
        ;;
    esac

5.1.5. for 循环语句

语法格式

#!/bin/bash

for 值 in 列表
do
    执行语句
done
- for 循环总是接收 in 语句之后的某种类型的字列表 - 执行次数和 list 列表中常数或字符串的个数相同,当循环的数量足够了,就自动退出

案例:遍历文件

#!/bin/bash

# for 循环语句验证
for i in $(ls /root)
do
    echo "/root 目录下有文件: ${i}"
done

5.1.6. while 循环语句

语法格式

#!/bin/bash

while 条件
do
    执行语句
done

实例

#!/bin/bash
# while 语法演示

a=1
while [ "${a}" -lt 5 ]
do
    echo "${a}"
    a=$((a+1))
done

5.1.7. until 循环语句

语法格式

until 条件
do
    执行语句
done
- 条件类型:命令/ [[ 字符串表达式 ]]/(( 数字表达式 ))

实例

#!/bin/bash
# until 语法演示

a=1
while [ "${a}" -ne 5 ]
do
    echo "${a}"
    a=$((a+1))
done

5.2. 复杂流程控制

5.2.1. 函数基础

简单函数

#!/bin/bash

# 函数定义
函数名(){
    函数体
}

# 调用函数
函数名

传参函数

#!/bin/bash

# 函数定义
函数名(){
    函数体 ${n}
}

# 直接调用函数
函数名 参数

# 执行脚本传参
函数名 "$1"
- 注意:这里的参数类似于Shell内置变量中的位置参数

6. 综合案例

6.1. 需求

  1. 脚本采用传递一个参数才能执行,没有或者多余参数将会提示脚本的帮助信息
  2. 脚本通过传入参数来实现不同的功能,具体参数和功能详情如下:
参数 执行效果
start 服务启动中..
stop 服务关闭中..
restart 服务重启中..
* 脚本帮助信息
  1. 帮助信息部分使用函数来实现,信息内容如下:
  2. 脚本x.sh使用方式 x.sh [start|stop|restart]

6.2. 分析

  1. 脚本执行需要传递参数才能执行
    • 脚本传参
    • case 语句
  2. 脚本所传递的参数有限制:only one
    • 条件表达式的参数数量判断
    • if 语句
  3. 帮助信息使用函数来实现
    • 函数定义
    • 函数调用
  4. if 语句和 case 语句的嵌套使用

6.3. 实现

#!/bin/bash
# Bash 综合案例

file_name=$0

help(){

    echo "脚本 '${file_name}' 使用方式:'${file_name}' [start|stop|restart]"

}

if [ "$#" -ne 1 ]
then
    help file_name
else
    arg="$1"
    case "${arg}" in
        "start")
            echo "服务启动中.."
            ;;
        "stop")
            echo "服务关闭中.."
            ;;
        "restart")
            echo "服务重启中.."
            ;;
        "*")
            help file_name
            ;;
        esac
fi