一、Shell 脚本基础认知
1. 解释型 vs 编译型语言
| 类型 | 代表语言 | 执行原理 | 优缺点 |
|---|---|---|---|
| 解释型 | Shell、Python、Perl | 无需编译,由解释器逐行解析执行 (如 Bash 解释器解析 .sh 脚本) |
优点:开发效率高、修改后可直接执行;缺点:运行效率低于编译型,依赖解释器 |
| 编译型 | C/C++、Go、Java(半编译) | 先通过编译器将源代码转换为机器可识别的二进制指令,再执行生成的可执行文件 | 优点:运行效率高、不依赖编译器(仅需运行可执行文件);缺点:开发效率低,修改后需重新编译 |
2.Shell 脚本的两种执行方式(核心区别:权限与子 Shell)
bash
# 格式:解释器名称 脚本文件路径
bash a.sh
sh a.sh # sh 是 bash 的简化版,部分语法不兼容
- 无需为脚本授予执行权限 (
x权限); - 会启动子 Shell 进程执行脚本,脚本内的变量、环境修改仅在子 Shell 中有效,执行完毕后子 Shell 退出,修改不影响当前终端环境。
(2)脚本自身作为可执行文件执行
bash
# 第一步:授予执行权限(仅需执行一次)
chmod +x a.sh
# 第二步:执行脚本(需指定路径,./ 表示当前目录)
./a.sh # 相对路径执行
/home/user/a.sh # 绝对路径执行
- 必须先通过
chmod +x授予执行权限; - 脚本开头需指定解释器(
#!/bin/bash或#!/bin/sh),否则系统会使用默认解释器; - 默认同样启动子 Shell 进程 执行,若需在当前 Shell 执行,需使用
source或.命令。
(3)在当前 Shell 环境执行(保留脚本修改)
bash
# 两种格式等价,均在当前 Shell 中执行,不启动子进程
source a.sh
. a.sh # 注意:. 与脚本路径之间有空格
- 无需授予执行权限;
- 脚本内的变量赋值、环境变量修改、函数定义等会直接作用于当前终端环境,执行后永久保留(直到终端关闭)。
3. 脚本头部指定解释器
bash
#!/bin/bash # 指定该脚本由 Bash 解释器解析执行(推荐,功能更完善)
#!/bin/sh # 指定该脚本由 sh 解释器解析执行(兼容性强,功能有限)
- 该语句必须位于脚本第一行第一列 ,以
#!开头(称为 Shebang 符号); - 若省略该语句,系统会使用当前终端的默认 Shell 解释器执行脚本。
二、Shell 变量(无类型,核心注意格式)
1. 变量的定义与赋值(严格遵循格式)
(1)核心规则
- 变量名由字母、数字、下划线组成,不能以数字开头;
- 赋值符号
=左右无任何空格(Shell 语法的严格要求,多空格会被解析为命令); - 变量无类型区分,默认存储为字符串,数值运算需额外处理;
- 字符串值包含空格、特殊字符 时,必须用双引号
"或单引号'包裹。
(2)示例
bash
# 合法定义
val=100 # 纯数字(无空格,无需引号)
str="hello world" # 包含空格,用双引号包裹
str2='hello $val' # 包含特殊字符($),用单引号包裹
name=zhangsan # 纯字符串(无空格,无需引号)
# 非法定义(会报错)
val = 100 # = 左右有空格
str=hello world # 包含空格,未加引号
1name=zhangsan # 以数字开头
2. 变量的取值与输出
(1)取值语法:$变量名 或 ${变量名}
$变量名:简单取值,适用于无歧义场景;${变量名}:明确变量边界,避免与后续字符混淆(推荐,更严谨)。
(2)输出命令:echo(核心)
bash
val=100
# 简单输出变量
echo $val # 输出:100
echo ${val} # 输出:100,与上面等价
# 变量嵌入字符串输出(推荐用双引号,可解析变量)
echo "val的取值是:$val" # 输出:val的取值是:100
echo "val+10的结果是:$((val+10))" # 输出:val+10的结果是:110(简单数值运算)
# 避免变量解析(单引号,原样输出)
echo 'val的取值是:$val' # 输出:val的取值是:$val
3. 变量的作用域(全局变量 vs 局部变量)
(1)全局变量(默认)
- 定义:直接赋值,无
local关键字修饰; - 作用域:整个脚本有效,若在当前 Shell 执行脚本(
source/.),则终端环境中也有效; - 注意:父子 Shell 中,子 Shell 会继承父 Shell 的全局变量,但子 Shell 对变量的修改不会影响父 Shell。
(2)局部变量(仅函数内有效)
- 定义:在函数体内 用
local关键字修饰; - 作用域:仅当前函数内部有效,函数执行完毕后自动销毁,不会污染全局变量;
- 示例:
bash
#!/bin/bash
# 全局变量
global_val=10
# 定义函数
test_fun() {
# 局部变量(仅函数内有效)
local local_val=20
echo "函数内:local_val=$local_val"
echo "函数内:global_val=$global_val" # 函数内可访问全局变量
}
# 调用函数
test_fun
# 函数外访问局部变量(无输出,变量不存在)
echo "函数外:local_val=$local_val"
echo "函数外:global_val=$global_val"
4. 特殊变量(系统预定义,无需手动赋值)
Shell 提供了一系列预定义的特殊变量,用于获取脚本执行时的命令行参数、进程信息等,核心常用如下:
| 特殊变量 | 含义说明 |
|---|---|
$0 |
脚本的文件名(包含执行路径,如 ./a.sh 或 /home/user/a.sh) |
$1~$n |
脚本的命令行参数,$1 是第一个参数,$2 是第二个参数,以此类推($10 需写为 ${10}) |
$# |
脚本的命令行参数总个数 |
$* |
所有命令行参数的集合,将所有参数视为一个整体(用双引号包裹时:"$*"等价于"1 2 ... $n") |
$@ |
所有命令行参数的集合,将每个参数视为独立个体(用双引号包裹时:"$@"等价于"1" "2"..."$n",推荐使用) |
$$ |
当前脚本进程的 PID(进程 ID) |
$? |
上一条命令 / 脚本的执行返回状态码(0 表示执行成功,非 0 表示执行失败,具体值对应错误类型) |
$_ |
上一条命令的最后一个参数 |
示例:获取命令行参数
bash
#!/bin/bash
echo "脚本文件名:$0"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "参数总个数:$#"
echo "所有参数(整体):$*"
echo "所有参数(独立):$@"
执行命令:./a.sh hello world,输出结果:
cpp
脚本文件名:./a.sh
第一个参数:hello
第二个参数:world
参数总个数:2
所有参数(整体):hello world
所有参数(独立):hello world
5. 环境变量(全局有效,继承性)
(1)核心特性
- 环境变量是全局变量,可在当前 Shell 及所有子 Shell 中生效;
- 父子 Shell 之间存在继承关系:子 Shell 会继承父 Shell 的环境变量,但子 Shell 无法修改父 Shell 的环境变量。
(2)定义环境变量:export 关键字
bash
# 格式1:先定义变量,再导出为环境变量
val=300
export val
# 格式2:定义与导出一步完成(推荐)
export str="global environment variable"
3)常用系统环境变量
PATH:系统命令查找路径,新增自定义命令路径可追加:export PATH=$PATH:/home/user/bin;HOME:当前用户的主目录路径(如/home/user);USER:当前登录用户的用户名;PWD:当前工作目录的路径;SHELL:当前使用的 Shell 解释器路径(如/bin/bash)。
6. 命令替换(获取命令执行结果赋值给变量)
两种格式等价,推荐使用 $(命令)(可读性更强,支持嵌套):
bash
# 格式1:反引号 ``(注意:不是单引号,位于键盘 Tab 键上方)
str=`ls` # 将 ls 命令的执行结果(当前目录文件列表)赋值给 str
# 格式2:$(命令)(推荐,支持嵌套)
file_count=$(ls | wc -l) # 嵌套:先执行 ls,再通过管道传递给 wc -l 统计文件个数
dir_path=$(cd $(dirname $0) && pwd) # 获取脚本所在目录的绝对路径
三、Shell 流程控制(选择、循环)
1. 条件判断:if 语句(单分支 / 双分支 / 多分支)
(1)核心语法规则
- 条件判断表达式必须放在中括号
[]或双中括号[[]]中; [ ]左右必须保留空格 (if [ 条件 ]; then),这是 Shell 语法的严格要求,缺少空格会报错;- 语句结构:
if/elif后必须跟then,else后无需then,整个if语句以fi(if倒写)结尾; - 多条件组合:
[ ]中用-a(与)、-o(或),[[]]中用&&(与)、||(或)(推荐[[]],可读性更强); - 数值比较、字符串比较、文件判断有固定的运算符,不可混用。
(2)常用判断运算符
| 判断类型 | 运算符([ ] 中使用) |
含义说明 |
|---|---|---|
| 数值比较 | num1 -eq num2 |
等于(equal) |
num1 -ne num2 |
不等于(not equal) | |
num1 -gt num2 |
大于(greater than) | |
num1 -lt num2 |
小于(less than) | |
num1 -ge num2 |
大于等于(greater or equal) | |
num1 -le num2 |
小于等于(less or equal) | |
| 字符串比较 | str1 = str2 |
相等(注意:= 左右有空格) |
str1 != str2 |
不相等 | |
-z str |
字符串为空(长度为 0) | |
-n str |
字符串非空(长度大于 0) | |
| 文件判断 | -f file |
文件存在且为普通文件 |
-d dir |
目录存在且为目录 | |
-e path |
文件 / 目录存在 | |
-r file |
文件存在且有读取权限 | |
-w file |
文件存在且有写入权限 | |
-x file |
文件存在且有执行权限 |
(3)示例
单分支 if
bash
#!/bin/bash
read -p "请输入一个数字:" num
if [ $num -ge 60 ]; then
echo "及格"
fi
双分支 if-else
bash
#!/bin/bash
read -p "请输入一个数字:" num
if [[ $num -ge 60 && $num -le 100 ]]; then
echo "及格"
else
echo "不及格"
fi
多分支 if-elif-else
bash
#!/bin/bash
read -p "请输入一个数字(0-100):" num
if [[ $num -ge 90 && $num -le 100 ]]; then
echo "优秀"
elif [[ $num -ge 80 && $num -lt 90 ]]; then
echo "良好"
elif [[ $num -ge 60 && $num -lt 80 ]]; then
echo "及格"
else
echo "不及格"
fi
2. 多分支选择:case 语句(等价于 C 语言 switch)
(1)核心语法规则
- 结构:
case 变量名 in开头,esac(case倒写)结尾; - 模式匹配:
模式)结尾,支持通配符(*匹配任意内容,|匹配多个模式); - 分支结束:每个分支的命令执行完毕后,用
;;结尾(等价于 C 语言break,防止穿透); - 默认分支:用
*)匹配所有未定义的模式(等价于 C 语言default)。
(2)示例
bash
#!/bin/bash
read -p "请输入一个字符(a/b/c/其他):" char
case $char in
a|A) # 匹配小写 a 或大写 A
echo "你输入的是字母 A"
;;
b|B)
echo "你输入的是字母 B"
;;
c|C)
echo "你输入的是字母 C"
;;
*) # 默认分支,匹配所有其他字符
echo "你输入的不是 a/b/c 中的字符"
;;
esac
3. 循环语句:for / while
(1)for 循环(两种常用格式)
格式 1:遍历列表元素(常用,适用于已知循环次数)
bash
#!/bin/bash
# 格式:for 变量名 in 列表元素1 列表元素2 ... 列表元素n
for i in {1..10} # {1..10} 表示生成 1 到 10 的整数列表
do
# 循环体:通过 $i 获取当前遍历的元素
touch "file${i}.txt" # 创建 file1.txt 到 file10.txt
echo "已创建:file${i}.txt"
done
# 遍历自定义列表
for fruit in apple banana orange
do
echo "水果:$fruit"
done
格式 2:C 语言风格(适用于复杂循环条件)
bash
#!/bin/bash
# 格式:for (( 初始化表达式; 条件判断表达式; 增量/减量表达式 ))
sum=0
for (( i=1; i<=100; i++ ))
do
sum=$((sum + i)) # 累加 1 到 100
done
echo "1+2+...+100=$sum"
(2)while 循环(适用于未知循环次数,满足条件则循环)
bash
#!/bin/bash
# 格式:while [ 条件判断表达式 ]
num=1
sum=0
while [ $num -le 100 ]
do
sum=$((sum + num))
num=$((num + 1)) # 更新循环变量,避免无限循环
done
echo "1+2+...+100=$sum"
# 无限循环(两种格式等价)
while true
do
echo "无限循环中,按 Ctrl+C 退出"
sleep 1 # 休眠 1 秒
done
# 简化版无限循环
while :
do
echo "无限循环中,按 Ctrl+C 退出"
sleep 1
done
(3)循环控制:break / continue
-
break:跳出当前整个循环,不再执行后续循环体; -
continue:跳过当前循环的剩余内容,直接进入下一次循环。bash#!/bin/bash # 示例:打印 1 到 10,跳过 5,遇到 8 跳出循环 for i in {1..10} do if [ $i -eq 5 ]; then continue # 跳过 5,直接进入下一次循环 fi if [ $i -eq 8 ]; then break # 跳出整个循环,不再执行后续内容 fi echo "当前数字:$i" done四、Shell 函数(封装可复用逻辑)
1. 函数的定义(两种格式,推荐格式 2)
格式 1:简化格式(无
function关键字)bashfun_name() { # 函数体:可包含任意 Shell 命令、变量、流程控制 echo "函数 $fun_name 正在执行" echo "第一个参数:$1" echo "第二个参数:$2" }格式 2:标准格式(带
function关键字,可读性更强)bashfunction fun_name() { local num1=$1 # 局部变量,仅函数内有效 local num2=$2 local sum=$((num1 + num2)) echo "两数之和:$sum" }2. 函数的调用(无需括号,直接传参)
bash#!/bin/bash # 先定义函数(Shell 函数必须先定义后调用,无声明机制) function add() { local num1=$1 local num2=$2 echo $((num1 + num2)) } # 调用函数:格式为「函数名 参数1 参数2 ...」,无需加括号 result=$(add 10 20) # 捕获函数输出结果 echo "10+20=$result" add 5 8 # 直接调用,输出结果3. 函数的返回值(两种方式)
(1)
return语句(仅返回 0~255 的状态码) -
return 0表示函数执行成功,return 非0表示执行失败; -
调用后通过
$?获取返回值。bash#!/bin/bash function check_file() { if [ -f "$1" ]; then return 0 # 文件存在,返回成功 else return 1 # 文件不存在,返回失败 fi } # 调用函数 check_file "test.txt" if [ $? -eq 0 ]; then echo "文件存在" else echo "文件不存在" fi(2)
echo输出(返回任意数据,推荐) -
函数内通过
echo输出需要返回的数据,调用时通过命令替换$(函数名)捕获返回值; -
支持返回字符串、数值、复杂结果,灵活性更高。
bash#!/bin/bash function get_info() { local name=$1 local age=$2 echo "姓名:$name,年龄:$age" # 输出返回值 } # 捕获函数返回值 info=$(get_info "zhangsan" 20) echo $info五、Shell 文本处理三剑客(grep/awk/sed)
你已经梳理了
awk和sed的核心内容,这里提炼三者的核心定位和常用用法,形成清晰对比:1. 三剑客核心定位对比
工具 核心定位 典型用途 grep文本查找 / 匹配(过滤符合条件的行),基于正则表达式 查找日志中的错误信息、筛选包含指定关键字的行、统计匹配行数 awk文本分析 / 统计 / 格式化(处理结构化文本,按列提取、计算),自带编程语言特性 统计文件行数 / 列数、提取 CSV 文件的指定列、计算数值总和、格式化文本输出 sed文本编辑 / 修改(逐行替换、删除、插入、追加),基于流编辑 批量替换配置文件中的参数、删除文本中的注释行、在指定行插入内容、修改文件名 2. 核心用法示例(快速上手)
(1)
grep(查找核心)bash# 基础用法:查找包含 "error" 的行 grep "error" log.txt # 忽略大小写 + 显示行号 + 递归查找(查找目录下所有文件) grep -i -n -r "warning" /home/user/logs # 反向匹配:查找不包含 "info" 的行 grep -v "info" log.txt(2)
awk(分析核心,结构化文本处理)bash# 基础用法:打印文件的第 1 列和第 3 列(默认分隔符:空格/制表符) awk '{print $1, $3}' data.txt # 指定分隔符(逗号),处理 CSV 文件 awk -F "," '{print $2, $4}' test.csv # 统计文件总行数(NR 为内置变量,标识当前行号) awk 'END {print NR}' data.txt # 计算第 2 列的总和 awk '{sum += $2} END {print "总和:", sum}' data.txt(3)
sed(编辑核心,批量修改文本)bash# 基础用法:全局替换 "old" 为 "new"(仅输出,不修改原文件) sed 's/old/new/g' test.txt # 直接修改原文件(-i,慎用,建议先备份) sed -i.bak 's/old/new/g' test.txt # -i.bak 生成备份文件 test.txt.bak # 删除第 2 到第 8 行 sed '2,8d' test.txt # 在第 3 行上方插入内容 sed '3i\===== 插入的表头 =====' test.txt # 替换第 5 行的整行内容 sed '5c\这是替换后的第 5 行' test.txt六、Shell 脚本实操注意事项(避坑指南)
-
语法严格性 :Shell 对空格、引号、符号的格式要求极高,
=左右无空格、[ ]左右有空格、字符串包含空格需用双引号包裹; -
变量边界 :取值时尽量用
${变量名}代替$变量名,避免与后续字符混淆(如${i}txt而非$itxt); -
权限问题 :执行脚本前确认是否有对应文件的读取 / 执行权限,修改系统文件时需用
sudo提升权限; -
备份优先 :使用
sed -i直接修改原文件前,务必先备份(-i.bak),避免误操作导致数据丢失; -
调试脚本 :通过
bash -x a.sh执行脚本,可打印脚本的执行过程(逐行解析命令),便于排查语法错误和逻辑问题。