Shell脚本编程
一、Shell基础知识
1,什么是shell脚本
当你配置5台机甚至以上的时候,你用ssh链接一台台机子敲命令的时觉得太麻烦的时候,我觉得你需要用shell就脚本了,环境句话说你需要输入重复命令的时候,shell可以帮你做这些脏话,累活。
应用场景:
只有你想不到,没有他做不到,除非你叫他生孩子。(人至少,不应该)
由于是Shell脚本程序,通常期后缀名.sh,代码编写完成后,还需要为其添加x权限,通过./脚本执行。
最好使用:sh +x 脚本名.sh
小结:
(1)Shell解析器:解析用户输入的命令或者程序
(2)Shell应用场景:简化运维流程、日常巡检、批量化软件安装与配置等等
2,Shell编程的变量
变量:1.存储数据容器
2.临时的数据
3.可发生改变
变量核心作用?存储数据
基本语法:
变量=变量的值
x=3
# 含义:将3赋值给这个变量x,在接下来的程序中x这个变量为3,除非在接下来的程序中重新赋值。
name='王者荣耀真的太公平了(磊哥音)'
echo $name
王者荣耀真的太公平了(磊哥音)
# 注意事项:=等号两边不能有空格 !变量名可以自定义,但是要满足一定的命名规则。
总结:
变量核心作用?存储数据
变量有三句话:① 变量存储数据的容器 ② 变量中存储的数据是临时的 ③ 变量中存储的数据可以发生变化
3. 变量的命名规则
变量名称只能由==字母、数字或下划线==组成
不能以==数字==开头
严格==区分大小写==
不能使用Shell中保留的关键字 => test、if、for
总结:
变量的命名规则,变量不能随意命名,只能是字母 + 数字 + 下划线组成,不能以数字开头,区分大小写
建议使用英文单词或者拼音作为变量名称
4.变量的种类
环境变量(全局变量)和普通变量(局部变量)
| 特性 | 环境变量 | 局部变量 |
|---|---|---|
| 定义与作用域 | 在操作系统或Shell会话级别定义,对其所有子进程都可见。 | 在特定的脚本、函数或进程内部定义,仅在其被定义的上下文中有效。 |
| 生命周期 | 持久或会话级。可以永久保存(写入配置文件),或在当前Shell会话关闭前一直有效。 | 临时。随着脚本、函数或进程的结束而立即销毁。 |
| 主要用途 | 1. 系统配置(如 PATH, HOME)。 2. 传递配置信息给应用程序(如 JAVA_HOME, DATABASE_URL)。 3. 影响系统和程序的行为。 | 1. 在脚本或程序中临时存储中间计算结果。 2. 作为循环计数器、函数参数等。 3. 封装逻辑,避免命名冲突。 |
| 设置方法(以Linux Bash为例) | 临时设置: export VAR_NAME=value 永久设置: 写入 ~/.bashrc, ~/.profile 或 /etc/environment 等文件。 | 直接赋值: var_name=value (不加 export 关键字) |
| 如何访问 | 在子进程中可直接读取。例如,在程序内部通过 getenv(“PATH”)(C语言)或 os.environ[“PATH”](Python)访问。 | 只能在定义它的脚本或函数内部通过变量名直接访问。 |
| 影响范围 | 广泛。影响从该环境启动的所有命令行工具、图形程序、服务等。 | 狭窄。仅限于其所属的代码块,对外部完全不可见。 |
| 类比 | 公司的全局规章制度:所有部门(子进程)都必须遵守和知晓。 | 部门内部的临时会议纪要:只在本部门内部有效,其他部门不知道也不关心。 |
5,环境变量
如何设置环境变量?
exprt 变量名=变量的值
☆ 临时设置:可以在命令行中直接设置,但退出会话后会丢失。
export MYHOME=/home/itheima
☆ 永久设置:如果希望变量在每次登录时都有效,需要将其添加到配置文件中:
用户级别:~/.bash_profile 或 ~/.bashrc
系统级别:/etc/profile 或 /etc/bashrc
所有环境变量名称通常采用大写字母,且必须使用 export 命令导出,才能让其对子进程有效。
6,显示与取消环境变量
echo $HOME # 用户的家目录
echo $UID # 当前用户的UID,相当于id –u
echo $PWD # 当前工作目录的绝对路径名
echo $SHELL # 当前SHELL
echo $USER # 当前用户
显示环境变量
echo $USER
itheima
用unset消除本地变量和环境变量
unset USER
echo $USER
unset移除变量时,不需要添加$美元符号,直接使用变量名称即可
总结:
获取变量信息 => (echo $变量名称)
移除/消除某个变量 => (unset 变量名称)
7.环境变量初始化与对应文件的生效顺序
登录 Shell vs 非登录 Shell
| 特征 | 登录 Shell(Login Shell) | 非登录 Shell(Non-login Shell) |
|---|---|---|
| 启动方式 | 用户登录系统 | 从现有 Shell 打开新终端 |
| 认证 | 需要用户名/密码 | 不需要重新认证 |
| 配置文件 | 加载完整的配置文件集 | 仅加载部分配置文件 |
| 环境变量 | 完整的环境变量设置 | 继承父 Shell 环境 |
$0 值 | 以 - 开头(如 -bash) | 不以 - 开头(如 bash) |
一. 登录 Shell 启动顺序
当以登录方式启动 Shell 时:
text
/etc/profile → /etc/profile.d/*.sh → ~/.bash_profile → ~/.bash_login → ~/.profile → ~/.bashrc
二. 非登录 Shell 启动顺序(crontab模式之类)
text
~/.bashrc → /etc/bashrc(某些系统)
非登录shell只会加载$HOME/.bashrc (用户环境变量文件),并寻找**/etc/bashrc (全局环境变量文件)**
小结:
用户环境变量配置在:$HOME/.bashrc文件中
全局环境变量配置在:/etc/bashrc文件中
不管用户还是全局的环境变量,建议不要放在$HOME/.bash_profile和/etc/profile中
8.案例:Shell设置登陆提示的两种方式
第一种:/etc/profile.d/下面增加如下脚本
[root@localhost ~]# cat /etc/profile.d/myshell.sh
echo 'ovo ovo ovo ovo ovo'
注意事项:若要在登陆后初始化或显示加载内容,则把脚本文件放在/etc/profile.d/下即可(无须加执行权限)
第二种方式:在/etc/motd里增加提示的字符串(没试过,可以试试)
[root@itheima ~] # cat /etc/motd # 文件里仅为字符串内容
welcome to itheima yunwei training.
9.单引号与双引号的区别
Shell(Bash/Zsh)
-
单引号:完全原样输出,不解析变量和转义符
bash
echo '$HOME' # 输出:$HOME -
双引号:会解析变量和部分转义符
bash
echo "$HOME" # 输出:/home/username
10.把命令执行结果赋值给变量
变量名=`ls` # ``反撇号
变量名=$(ls) # $(命令)
案例:
[root@localhost ~]# a=`ls`
[root@localhost ~]# echo $a
anaconda-ks.cfg jdk-8u241-linux-x64.tar.gz shell02_2026_02_09.sh
# 用的很少,几乎不用
[root@localhost ~]# a=$(ls)
[root@localhost ~]# echo $a
anaconda-ks.cfg jdk-8u241-linux-x64.tar.gz shell02_2026_02_09.sh
# 用的很多
小结:
可以把命名的执行结果赋值给变量 =>
变量=命令
变量=$(命令)
11,Shell中特殊且重要的内置变量
$0 # 当前脚本名称
$1 $2 $3 ... ${10} # 位置参数
$# # 参数个数
$@ # 所有参数(每个独立)
$* # 所有参数(合并为一个字符串)
案例演示:
[root@localhost ~]# cat test.sh
#!/bin/bash
# test.sh
echo "脚本名: $0"
echo "第一个参数: $1"
echo "参数总数: $#"
echo "所有参数: $@"
# 运行: ./test.sh a b c
[root@localhost ~]# bash test.sh a b c
脚本名: test.sh
第一个参数: a
参数总数: 3
所有参数: a b c
12,shell运算符
1. 算术运算符
默认情况下,Shell就只能支持简单的整数运算
+ - * / %(取模,求余数)
数值的运算,expr、let或者双括号(()):
① 使用 $(( 表达式 )) => 新版,推荐表达式两边保留一个空格
② 使用$[ 表达式 ] => 老版,推荐表达式两边保留一个空格
③ 使用 expr 外部程式,乘法、括号需要加反斜杠转义 => expr 1 \* 2 => 注意:运算符两边必须有空格
④ 使用let 命令,赋值并运算,支持++、--,++在原有变量基础上+1,--在原有变量值基础上减-1
# 使用 expr
echo `expr 10 + 2` # 加法: 12
echo `expr 10 - 2` # 减法: 8
echo `expr 10 \* 2` # 乘法: 20 (注意*需要转义)
echo `expr 10 / 2` # 除法: 5
echo `expr 10 % 3` # 取余: 1
# 使用 let
let "sum=10+5"
echo $sum # 15
# 使用双括号
echo $((10 + 5)) # 15
echo $((10 * 5)) # 50
echo $((10 / 3)) # 3 (整数除法)
echo $((10 % 3)) # 1
echo $((10 ** 2)) # 100 (幂运算,bash支持)
2. 关系运算符
用于数值比较(返回布尔值):
| 运算符 | 说明 | 示例 |
|---|---|---|
-eq | 等于 | [ $a -eq $b ] |
-ne | 不等于 | [ $a -ne $b ] |
-gt | 大于 | [ $a -gt $b ] |
-lt | 小于 | [ $a -lt $b ] |
-ge | 大于等于 | [ $a -ge $b ] |
-le | 小于等于 | [ $a -le $b ] |
a=10
b=20
if [ $a -eq $b ]; then
echo "a 等于 b"
elif [ $a -lt $b ]; then
echo "a 小于 b" # 输出这个
fi
总结:
整数运算 => [],如果有++或--的情况,建议使用let
默认不支持小数运算 => dnf install bc -y => `echo 1+1.5 |bc
二、Shell编程小工具(重点)
1、cut工具
cut 列 的截取工具,用于列的截取
语法:
# cut 选项 文件名
常用参数
| 选项 | 说明 |
|---|---|
-d | 指定字段分隔符(默认为TAB) |
-f | 指定要提取的字段(列) |
-c | 按字符位置提取 |
-b | 按字节位置提取 |
--complement | 提取指定范围之外的列 |
--output-delimiter | 指定输出分隔符 |
实际示例
实例文件employees.txt
John:Doe:35:Engineer:50000
Jane:Smith:28:Designer:45000
Bob:Johnson:42:Manager:60000
1.提取姓名(第1、2列)
[root@localhost ~]# cut -d ':' -f 1,2 employees.txt
John:Doe
Jane:Smith
Bob:Johnson
2.提取职位和薪水
[root@localhost ~]# cut -d ':' -f 4,5 employees.txt
Engineer:50000
Designer:45000
Manager:60000
3.提取特定范围的列
[root@localhost ~]# cut -d ':' -f 1,5 employees.txt
John:50000
Jane:45000
Bob:60000
小结:
cut作用?用于截取指定列的信息
-c:字符
-d、-f:-d指定分隔符,-f指定截取第几列
2.sort工具
用于对文本内容进行排序
sort [选项] [文件]
常用选项
| 选项 | 说明 |
|---|---|
-r | 逆向排序(降序) |
-n | 按数值大小排序(而不是字典序) |
-k n | 按第 n 列排序(列默认以空格或制表符分隔) |
-t ',' | 指定分隔符(例如逗号分隔的 CSV 文件) |
-u | 去重,只保留唯一的行 |
-f | 忽略大小写 |
-o 文件 | 将结果输出到指定文件(可覆盖原文件) |
-R | 随机排序(打乱顺序) |
-h | 按人类可读的数值排序(如 2K, 1G) |
# sort -n -t: -k3 1.txt 按照用户的uid进行升序排列
# sort -nr -t: -k3 1.txt 按照用户的uid进行降序排列
# sort -n 2.txt 按照数字排序
# sort -nu 2.txt 按照数字排序并去重
# sort -nr 2.txt 按照数字进行降序排列
# sort -nru 2.txt 按照数字进行降序排列并去重
# sort -n 2.txt -o 3.txt 按照数字排序并将结果重定向到文件
# sort -R 2.txt 随机排序
# sort -u 2.txt 去除重复行
示例
首先,创建一个包含混合数据的文件:
# 创建测试文件
cat > mixed_data.txt << 'EOF'
Alice 25 Engineer 55000
Bob 30 Manager 75000
Charlie 22 Intern 30000
Alice 25 Engineer 55000
david 28 Designer 48000
Eve 35 Director 95000
Frank 40 CEO 120000
grace 32 Analyst 52000
Henry 29 Developer 60000
100 Sales 45000
50 Marketing 40000
2000 Admin 35000
EOF
# 创建CSV格式文件
cat > employees.csv << 'EOF'
ID,Name,Age,Department,Salary
101,Alice,25,Engineering,55000
102,Bob,30,Management,75000
103,Charlie,22,Intern,30000
104,David,28,Design,48000
105,Eve,35,Executive,95000
106,Frank,40,Executive,120000
107,Grace,32,Analytics,52000
108,Henry,29,Engineering,60000
EOF
# 创建带文件大小的文件
cat > file_sizes.txt << 'EOF'
file1.txt 1.5K
file2.txt 200M
file3.txt 1.2G
file4.txt 800K
file5.txt 50M
file6.txt 2.3G
file7.txt 500B
EOF
# 创建纯数字文件
cat > numbers.txt << 'EOF'
42
7
100
15
999
3
256
EOF
# 创建带重复项的单词列表
cat > words.txt << 'EOF'
banana
apple
Cherry
date
Apple
banana
Fig
grape
EOF
基本排序演示
1.默认字典排序(words.txt为例)
[root@localhost ~]# sort words.txt
apple
Apple
banana
banana
Cherry
date
Fig
grape
2.忽略大小写排序(words.txt为例)
[root@localhost ~]# sort -f words.txt
Apple
apple
banana
banana
Cherry
date
Fig
grape
# 字母a-z排列,大小写大写为先
3.数值排序 (numbers.txt为例)
[root@localhost test1]# sort -n numbers.txt
3
7
15
42
100
256
999
4.逆序排序 (numbers.txt为例)
=== 数值逆序排序 (-nr) ===
[root@localhost test1]# sort -nr numbers.txt
999
256
100
42
15
7
3
=== 字典逆序排序 ===
[root@localhost test1]# sort -r words.txt
grape
Fig
date
Cherry
banana
banana
Apple
apple
5.按第二列数值排序(mixed_data.txt)
[root@localhost test1]# sort -n -k 2 mixed_data.txt
100 Sales 45000
2000 Admin 35000
50 Marketing 40000
Charlie 22 Intern 30000
Alice 25 Engineer 55000
Alice 25 Engineer 55000
david 28 Designer 48000
Henry 29 Developer 60000
Bob 30 Manager 75000
grace 32 Analyst 52000
Eve 35 Director 95000
Frank 40 CEO 120000
6.按第4列数值逆序排序(mixed_data.txt)
# sort -nr -k 4 mixed_data.txt
Frank 40 CEO 120000
Eve 35 Director 95000
Bob 30 Manager 75000
Henry 29 Developer 60000
Alice 25 Engineer 55000
Alice 25 Engineer 55000
grace 32 Analyst 52000
david 28 Designer 48000
100 Sales 45000
50 Marketing 40000
2000 Admin 35000
Charlie 22 Intern 30000
3.uniq工具
用于过滤或报告相邻的重复行。它通常与 sort 命令结合使用,因为 uniq 的核心机制是检测连续的相同行。
常用选项
| 选项 | 功能描述 |
|---|---|
-c | 在每行前面加上重复次数 |
-d | 只输出重复的行(至少出现两次) |
-D | 输出所有重复的行 |
-u | 只输出不重复的行(唯一行) |
-i | 忽略大小写差异 |
-f N | 跳过前 N 个字段进行比较 |
-s N | 跳过前 N 个字符进行比较 |
-w N | 只比较前 N 个字符 |
假设有一个文件data.txt,内容为
apple
banana
apple
Apple
banana
cherry
banana
1. 基本去重(仅相邻行)
uniq data.txt
# 由于apple单词没有重复,所以不会被去重
# 输出
apple
banana
apple
Apple
banana
cherry
banana
2. 先排序,再去重(全局去重)
这是最经典、最常用的组合。
[root@localhost test1]# sort data.txt | uniq
apple
Apple
banana
cherry
# 所有重复项都被移除
# 注意:apple 和 Apple 因大小写不同被视为不同的行。
3. 统计每行重复次数 (-c)
通常先排序再统计。
[root@localhost test1]# sort data.txt | uniq -c
2 apple
1 Apple
3 banana
1 cherry
# 可以用此统计日志中的错误频率、统计ip的访问次数
4. 只查看重复的行 (-d)
[root@localhost test1]# sort data.txt | uniq -d
apple
banana
5. 只查看唯一的行 (-u)
[root@localhost test1]# sort data.txt | uniq -u
Apple
cherry
6. 忽略大小写进行比较 (-i)
sort data.txt | uniq -i -c
3 apple
3 banana
1 cherry
7. 跳过字段进行比较 (-f)(应该用的少,还比较麻烦)
假设有文件 users.txt:
01 alice engineer
02 bob designer
03 alice manager
4、tee工具
tee 命令就像管道系统中的“T型三通”,它读取标准输入(比如前一个命令的输出),并同时将数据写入标准输出(屏幕)和一个或多个文件。
| 功能场景 | 基本命令格式 | 说明与示例 | ||||
|---|---|---|---|---|---|---|
| 基础写入文件 | `命令 | tee 文件名` | 将输出显示在屏幕并保存到文件。示例:`ls -l | tee file_list.txt` | ||
| 追加到文件 | `命令 | tee -a 文件名` | 使用 -a 选项,将新内容追加到文件末尾,而不是覆盖原文件。示例:`echo "new line" | tee -a log.txt` | ||
| 写入多个文件 | `命令 | tee 文件1 文件2` | 将同一份输出同时保存到多个文件中。示例:`echo "content" | tee backup1.txt backup2.txt` | ||
| 隐藏屏幕输出 | `命令 | tee 文件名 > /dev/null` | 将输出保存到文件,但不在终端显示。示例:`ls | tee list.txt > /dev/null` | ||
| 管道中间处理 | `命令A | tee 文件 | 命令B` | 将命令A的输出保存到文件,同时传递给命令B做进一步处理。示例:`ls -l | tee files.txt | grep ".txt"` |
| 写入需特权文件 | `命令 | sudo tee 文件名` | 与 sudo 结合,向需要root权限的文件(如 /etc/hosts)写入内容。示例:`echo "new entry" | sudo tee -a /etc/hosts` | ||
| 忽略中断信号 | `命令 | tee -i 文件名` | 使用 -i 选项,即使命令被 Ctrl+C 中断,tee 已写入文件的操作也不会异常退出。适用于长时间运行的命令(如 ping)日志记录。 |
总结:
双向操作:① 打印输出到屏幕 ② 把执行结果覆盖/追加到指定文件中
5、diff工具
diff 是一个用于比较文件或目录差异的命令行工具,在 Unix/Linux、macOS 和 Windows(通过工具包)中广泛使用。
基本语法
diff [选项] 文件1 文件2
1. 基本文件比较
# 比较两个文件
diff file1.txt file2.txt
默认情况下,diff 的输出格式为 正常格式,其符号含义如下:
<表示文件1中的内容>表示文件2中的内容- c 表示修改(change)
- a 表示增加(add)
- d 表示删除(delete)
6、paste工具
在 Linux/Unix 系统 中常用的命令行工具,用于按行合并多个文件,或者将标准输入的行与文件合并。它默认使用制表符(Tab)作为分隔符,将不同文件中对应的行连接成一行。
基本语法
bash
paste [选项] [文件1] [文件2] ...
常用选项
| 选项 | 说明 |
|---|---|
-d | 指定分隔符(默认为制表符) |
-s | 串行处理:将一个文件的所有行合并为一行 |
-z | 用 NUL 字符(\0)分隔行,而不是换行符 |
1. 合并两个文件(默认用制表符分隔)
bash
$ cat file1.txt
A
B
C
$ cat file2.txt
1
2
3
$ paste file1.txt file2.txt
A 1
B 2
C 3
2. 指定分隔符(如逗号)
bash
$ paste -d ',' file1.txt file2.txt
A,1
B,2
C,3
3. 合并三个文件
bash
$ paste file1.txt file2.txt file3.txt
A 1 X
B 2 Y
C 3 Z
4. 串行合并(-s)
将一个文件的所有行合并为一行:
bash
$ paste -s file1.txt
A B C
$ paste -s -d ',' file1.txt
A,B,C
5. 从标准输入合并
bash
$ echo -e "X\nY\nZ" | paste file1.txt -
A X
B Y
C Z
7、tr工具
tr(translate/transform)是一个简单而强大的命令行字符转换工具,主要用于替换、删除或压缩来自标准输入的字符。它通常与管道(|)结合使用,是 Unix/Linux 系统管理和文本处理中的常用工具。
核心概念
-
一对一替换:
tr处理的是单个字符,而不是字符串。它将一个字符集中的每个字符,映射到另一个字符集中对应的字符。 -
流处理:它从标准输入读取数据,将结果输出到标准输出。
-
语法基础:
tr [选项] 字符集1 [字符集2]
| 字符串 | 含义 |
|---|---|
| ==a-z== | 匹配所有小写字母 |
| ==A-Z== | 匹配所有大写字母 |
| ==0-9== | 匹配所有数字 |
| ==0-9a-zA-Z== | 匹配所有数字与大小写字母 |
主要功能和用法
1. 基本字符替换
将字符集1中的字符,替换成字符集2中对应位置的字符。
bash
echo "hello world" | tr 'a-z' 'A-Z'
# 输出:HELLO WORLD
echo "12345" | tr '123' 'abc'
# 输出:abccc (注意:'1'->'a', '2'->'b', '3'->'c', '4'和'5'不在集合1中,原样输出)
2. 删除字符 (-d)
删除所有出现在字符集1中的字符。
bash
echo "Phone: 123-456-7890" | tr -d '[:digit:]'
# 输出:Phone: --
echo "hello world" | tr -d 'ol'
# 输出:he wrd
3. 压缩连续字符 (-s)
将连续重复出现的字符压缩成一个(常用于格式化)。
bash
echo "hellooo worlddd" | tr -s 'o d'
# 输出:helloo world
# 经典用例:压缩多个空格为一个空格
echo "too many spaces" | tr -s ' '
# 输出:too many spaces
重要限制与注意事项
- 只处理字符,不处理字符串:
tr不能将"hello"替换为"world"。这种操作需要使用sed。 - 字符集长度:当进行替换操作时(两个字符集都给出),如果
字符集1比字符集2长,字符集2的最后一个字符会被重复,直到长度与字符集1匹配。这有时会导致非预期结果。 - 不支持正则表达式:
tr的匹配模式是固定的字符集,不支持*、.等正则元字符。