linux入门六:Linux Shell 编程

一、Shell 概述

1. 什么是 Shell?

Shell 是 Linux 系统中用户与内核之间的桥梁,作为 命令解析器,它负责将用户输入的文本命令转换为计算机可执行的机器指令。

  • 本质:Shell 是一个程序(如常见的 Bash、Zsh),而 Shell 脚本则是包含一系列命令的文本文件,通常以 .sh 结尾(非强制,仅为识别方便)。
  • 作用:批量执行重复命令、实现自动化任务、编写复杂逻辑程序。

2. 为什么选择 Bash?

  • 主流性:Bash(Bourne Again Shell)是 Linux 系统的默认 Shell,几乎所有发行版都内置,兼容性强。
  • 功能强大:支持变量、数组、函数、流程控制等高级特性,满足从简单脚本到复杂程序的需求。
  • 学习门槛低:语法简洁,对新手友好,且与早期 Shell(如 Bourne Shell)兼容。

二、Shell 脚本基础操作

1. 脚本文件的创建与编辑

1.1 选择文本编辑器

Shell 脚本本质是纯文本文件,推荐使用以下编辑器:

  • nano(新手友好):命令行下的简单编辑器,通过 nano 文件名 启动,支持鼠标和快捷键操作。
  • vim(功能强大):通过 vim 文件名 启动,需切换模式(i 进入插入模式,Esc 退出,:wq 保存并退出)。
  • gedit(图形界面):适合桌面环境,直接右键文件选择「用文本编辑器打开」。
1.2 创建脚本文件
# 在当前目录创建名为 demo.sh 的脚本文件
touch demo.sh
1.3 编写脚本内容
# 用 nano 打开并编辑脚本
nano demo.sh

输入以下内容:

#!/bin/bash
# 这是一个简单的 Shell 脚本示例
echo "Hello, World!"  # 输出文本

关键行解释

  • #!/bin/bash:指定脚本使用 Bash 解释器执行,必须位于文件第一行。
  • # 开头的行是注释,用于解释代码逻辑,不参与执行。
  • echo 命令用于输出文本,默认换行。若需不换行,使用 echo -n "内容"

2. 脚本的执行方式

2.1 方式一:通过 bash 命令执行(无需权限)
bash demo.sh

原理:直接调用 Bash 解释器执行脚本,适用于快速测试,无需修改文件权限。

2.2 方式二:通过 sh 命令执行(兼容旧版)
sh demo.sh

注意sh 通常指向 Bash,但某些系统中可能指向更古老的 Shell(如 BusyBox sh),可能导致兼容性问题。建议统一使用 #!/bin/bash 头部。

2.3 方式三:赋予执行权限后运行(推荐)
# 赋予文件执行权限
chmod +x demo.sh
# 通过相对路径执行
./demo.sh

关键点

  • chmod +x 用于添加执行权限,否则会提示 Permission denied
  • ./ 表示当前目录,必须显式指定路径,因为当前目录默认不在系统 PATH 中。

3. 脚本执行的常见问题与解决

3.1 权限不足

错误提示Permission denied
解决方法

chmod +x demo.sh  # 赋予执行权限
3.2 路径错误

错误提示No such file or directory
可能原因

  1. 脚本路径错误(如 ./demo.sh 写成 demo.sh)。
  2. 脚本文件格式问题(如 Windows 换行符导致的错误)。
    解决方法
# 检查路径是否正确
ls -l demo.sh  # 确认文件存在且路径正确# 转换文件格式为 Unix 格式(若文件来自 Windows)
dos2unix demo.sh  # 需先安装 dos2unix 工具
3.3 解释器路径错误

错误提示Bad interpreter: No such file or directory
可能原因:脚本头部 #!/bin/bash 路径错误。
解决方法

# 查看系统 Bash 路径
which bash  # 通常输出 /bin/bash# 修改脚本头部为正确路径
vim demo.sh  # 将第一行改为 #!/usr/bin/env bash(更具可移植性)

4. 脚本调试与验证

4.1 检查执行结果
# 执行脚本并查看输出
./demo.sh  # 正常输出:Hello, World!# 检查命令执行状态(0 表示成功)
echo $?  # 输出:0
4.2 调试模式
# 启用调试模式,显示每行执行的命令
bash -x demo.sh
4.3 错误处理
# 脚本遇到错误时立即退出
set -e# 捕获错误并输出信息
trap 'echo "错误发生在第 $LINENO 行"' ERR

5. 脚本优化与进阶

5.1 输出重定向
# 将输出保存到文件(覆盖原有内容)
./demo.sh > output.log# 追加输出到文件
./demo.sh >> output.log
5.2 输入重定向
# 从文件读取输入
cat input.txt | ./demo.sh
5.3 环境变量与脚本交互
# 在脚本中引用环境变量
echo "当前用户:$USER,主目录:$HOME"# 导出自定义变量到子进程
export MY_VAR="自定义变量"

6. 实战案例:批量创建用户

#!/bin/bash
# 批量创建用户脚本# 定义用户列表
users=("user1" "user2" "user3")# 遍历用户列表并创建用户
for user in ${users[@]}; douseradd $user && echo "用户 $user 创建成功" || echo "用户 $user 创建失败"
done

执行步骤

  1. 保存脚本为 create_users.sh
  2. 赋予执行权限:chmod +x create_users.sh
  3. 执行脚本:./create_users.sh

7. 常见易错点总结

  1. 变量赋值空格a = 10 错误,必须为 a=10
  2. 中括号空格[条件] 需写成 [ 条件 ](如 [ $a -gt 5 ])。
  3. 路径问题:执行脚本需用 ./脚本名,直接输入 脚本名 会提示 “命令未找到”。
  4. 转义符遗漏:使用 expr 计算乘法时,* 需转义为 \*(如 expr 5 \* 3)。
  5. 文件格式错误:Windows 格式文件需转换为 Unix 格式(使用 dos2unix)。

8. 拓展知识

8.1 脚本可移植性
  • 推荐头部#!/usr/bin/env bash,使用 env 命令自动查找 Bash 路径,避免硬编码。
  • 兼容性检查:使用 sh 命令测试脚本在旧版 Shell 中的运行情况。
8.2 权限设置最佳实践
  • 最小权限原则:仅赋予脚本所有者执行权限(chmod u+x 脚本名)。
  • 特殊权限setuidchmod u+s 脚本名)允许普通用户以脚本所有者权限执行。
8.3 脚本性能优化
  • 使用内置命令:优先使用 Bash 内置命令(如 echocd),避免调用外部程序。
  • 减少 I/O 操作:将多次 echo 合并为一次输出,或使用 printf 提升效率。

通过以上步骤,你可以全面掌握 Shell 脚本的基础操作,从创建、编辑到执行、调试,再到优化和实战应用。建议结合实际案例反复练习,加深对脚本执行原理的理解。

三、变量:脚本的 “数据细胞”

1. 变量基础:从存储到操作

1.1 变量定义与引用

定义变量

name="Alice"          # 字符串变量(值含空格需用引号包裹)
age=25                # 数值变量(本质为字符串,可参与计算)
file_path="/etc/passwd"  # 路径变量

关键规则

  • 变量名必须以字母或下划线开头,区分大小写(如 Name 和 name 是不同变量)。
  • 等号两边不能有空格(name = "Alice" 会报错)。

引用变量

echo "姓名:$name,年龄:${age}岁"
# 输出:姓名:Alice,年龄:25岁

推荐写法:使用 ${变量名} 避免歧义,例如:

fruit="apple"
echo "${fruit}s"  # 输出:apples(正确)
echo "$fruits"    # 输出:(错误,变量名歧义)
1.2 单引号 vs 双引号
符号特性示例
''原样输出,不解析变量和转义符echo '$name' → $name
""解析变量和转义符(如 \n 换行)echo "$name\n" → Alice 换行

实战场景

msg='当前用户:$USER,主目录:$HOME'
echo $msg  # 输出:当前用户:$USER,主目录:$HOMEmsg="当前用户:$USER,主目录:$HOME"
echo $msg  # 输出:当前用户:root,主目录:/root

2. 数值计算:从基础到高级

2.1 算术运算符
运算符示例(a=10b=3结果
+$((a + b))13
-$((a - b))7
*$((a * b))30
/$((a / b))3
%$((a % b))1

推荐方法

# 方法 1:$(( ))(简洁高效)
sum=$((5 + 3))          # 8
product=$((5 * 3))      # 15# 方法 2:expr(需转义乘号)
sum=$(expr 5 + 3)       # 8
product=$(expr 5 \* 3)  # 15(注意 `\*`)
2.2 数值运算实战

案例:计算圆的面积

#!/bin/bash
radius=5
area=$((3.14 * radius * radius))  # 注意:整数运算会截断小数
echo "半径为 $radius 的圆面积:$area"  # 输出:78(实际应为 78.5)

改进方案

area=$(echo "3.14 * $radius * $radius" | bc)  # 使用 bc 工具支持浮点运算
echo "半径为 $radius 的圆面积:$area"  # 输出:78.5

3. 标准变量:系统级的数据仓库

3.1 常用环境变量
变量名含义示例(管理员用户)
$HOME用户主目录/root
$PWD当前工作目录/home/user/scripts
$USER当前用户名root
$PATH命令搜索路径:/usr/bin:/bin
$HOSTNAME主机名localhost.localdomain

实战示例

echo "当前用户:$USER,主目录:$HOME"
# 输出:当前用户:root,主目录:/root
3.2 永久设置环境变量
  1. 临时生效(当前终端有效):
    export MY_VAR="自定义变量"
    
  2. 永久生效(所有终端有效):
    # 编辑用户配置文件
    nano ~/.bashrc
    # 在文件末尾添加
    export MY_VAR="自定义变量"
    # 使配置生效
    source ~/.bashrc
    

4. 特殊变量:脚本参数与状态

4.1 位置参数
变量含义示例(脚本 test.sh 1 2 "a b"
$0脚本名称test.sh
$1~$9第 1 到第 9 个参数$1=1$2=2$3=a b
${10}第 10 个参数(需用大括号)$10=10(若参数足够)

示例脚本 args.sh

#!/bin/bash
echo "脚本名:$0"
echo "第一个参数:$1"
echo "第十个参数:${10}"

运行:

./args.sh 1 2 3 4 5 6 7 8 9 10
# 输出:
# 脚本名:./args.sh
# 第一个参数:1
# 第十个参数:10
4.2 其他特殊变量
变量含义示例(脚本 test.sh
$#参数个数3(若传递 3 个参数)
$@所有参数(独立字符串)1 2 "a b"
$*所有参数(单个字符串)1 2 a b(空格丢失)
$$脚本进程号(PID)12345(实际 PID)
$?上条命令退出状态(0 = 成功)0(若命令成功)

实战案例

#!/bin/bash
echo "参数个数:$#"
echo "所有参数(\$@):$@"
echo "所有参数(\$*):$*"

运行:

./test.sh hello "world!"
# 输出:
# 参数个数:2
# 所有参数($@):hello world!
# 所有参数($*):hello world!

5. 变量作用域:从全局到局部

5.1 全局变量

定义:在脚本任何位置定义的变量,默认在整个脚本有效。

#!/bin/bash
global_var="全局变量"function show_var() {echo "函数内访问全局变量:$global_var"
}show_var  # 输出:函数内访问全局变量:全局变量
echo "函数外访问全局变量:$global_var"  # 输出:函数外访问全局变量:全局变量
5.2 局部变量

定义:使用 local 关键字在函数内定义的变量,仅在函数内有效。

#!/bin/bash
function local_var_demo() {local local_var="局部变量"  # 仅函数内有效echo "函数内访问局部变量:$local_var"
}local_var_demo  # 输出:函数内访问局部变量:局部变量
echo "函数外访问局部变量:$local_var"  # 输出:函数外访问局部变量:(空)

6. 高级变量操作:让脚本更灵活

6.1 变量替换
语法作用示例(str="hello world"
${str#h*o}从头部删除最短匹配 h*oworld
${str##h*o}从头部删除最长匹配 h*orld
${str%ld}从尾部删除最短匹配 ldhello wor
${str%%ld}从尾部删除最长匹配 ldhello wor
${str/world/Shell}替换第一个匹配项hello Shell
${str//l/LL}替换所有匹配项heLLo worLLd

实战案例

path="/home/user/documents/report.txt"
# 提取文件名
filename=${path##*/}  # 输出:report.txt
# 提取文件类型
extension=${filename##*.}  # 输出:txt
6.2 命令替换

语法

变量=$(命令)  # 推荐写法
变量=`命令`  # 反引号写法(易混淆)

示例

# 获取当前日期
date=$(date +%Y-%m-%d)
echo "今天日期:$date"  # 输出:今天日期:2023-10-01# 获取文件行数
line_count=$(wc -l < /etc/passwd)
echo "用户文件行数:$line_count"  # 输出:用户文件行数:42

7. 常见易错点与解决方案

7.1 变量赋值空格错误

错误示例

name = "Alice"  # 报错:-bash: name: 未找到命令

解决方案

name="Alice"  # 正确写法
7.2 中括号条件判断空格缺失

错误示例

if [ $age -gt 18 ]; then  # 正确
if [ $age-gt 18 ]; then  # 错误(缺少空格)
7.3 数组定义逗号分隔

错误示例

names=("Kanye", "Edison", "Fish")  # 错误(逗号分隔)

解决方案

names=("Kanye" "Edison" "Fish")  # 正确(空格分隔)
7.4 变量命名冲突

错误示例

USER="自定义用户"  # 覆盖系统变量 $USER

解决方案

user="自定义用户"  # 使用小写字母避免冲突

8. 拓展知识:让变量更强大

8.1 只读变量
readonly PI=3.14  # 定义只读变量
PI=3.1415        # 报错:PI: 只读变量
8.2 删除变量
name="Alice"
unset name       # 删除变量
echo $name       # 输出:(空)
8.3 变量类型转换
num="123"
echo $((num + 100))  # 输出:223(自动转换为整数)

9. 实战案例:变量综合应用

9.1 批量重命名文件
#!/bin/bash
# 将当前目录下所有 .txt 文件重命名为 .log
for file in *.txt; donew_name="${file%.txt}.log"  # 替换扩展名mv "$file" "$new_name"echo "重命名:$file → $new_name"
done
9.2 动态获取系统信息
#!/bin/bash
# 获取系统负载、内存使用、用户数
load=$(uptime | awk -F 'load average:' '{print $2}' | cut -d ',' -f 1)
mem_used=$(free -h | awk '/Mem:/ {print $3}')
user_count=$(who | wc -l)echo "系统负载:$load"
echo "内存使用:$mem_used"
echo "在线用户:$user_count"

10. 总结:变量的 “生存法则”

  • 命名规范:小写字母开头,避免与系统变量冲突。
  • 引号使用:值含空格或特殊字符时,优先使用双引号。
  • 作用域控制:函数内变量使用 local 声明,避免全局污染。
  • 性能优化:算术运算用 $(( )),命令替换用 $( )

通过以上内容,你将掌握 Shell 变量的核心操作,从基础定义到高级应用,再到实战案例,逐步提升脚本编写能力。变量是 Shell 编程的基石,熟练运用它们能让你的脚本更灵活、高效!

四、运算符与条件判断

1. 关系运算符(判断条件)

(1)数字比较
运算符含义示例(a=10b=20
-eq等于[ $a -eq $b ] → 假
-ne不等于[ $a -ne $b ] → 真
-gt大于[ $a -gt $b ] → 假
-lt小于[ $a -lt $b ] → 真
-ge大于等于[ $a -ge $b ] → 假
-le小于等于[ $a -le $b ] → 真
(2)字符串比较
运算符含义示例
-z字符串为空[ -z "" ] → 真
-n字符串非空[ -n "abc" ] → 真
==字符串相等[ "a" == "a" ] → 真
!=字符串不等[ "a" != "b" ] → 真
\>字符串排序大于(需转义)[ "b" \> "a" ] → 真
\<字符串排序小于(需转义)[ "a" \< "b" ] → 真
(3)文件判断
运算符含义示例
-e文件 / 目录存在[ -e /etc/passwd ] → 真
-f是普通文件[ -f first_script.sh ] → 真(若文件存在)
-d是目录[ -d /home ] → 真
-r文件可读[ -r /etc/shadow ] → 假(普通用户不可读)
-w文件可写[ -w first_script.sh ] → 真(若有写权限)
-x文件可执行[ -x first_script.sh ] → 真(若有执行权限)

2. 逻辑运算符(组合条件)

运算符含义示例
-a逻辑与(AND)[ $a -gt 5 -a $a -lt 15 ] → a 在 6-14 之间为真
-o逻辑或(OR)[ -f file -o -d dir ] → 文件或目录存在为真
!逻辑非(NOT)[ ! -e file ] → 文件不存在为真

注意

  • 条件判断需用中括号 [ ],且括号前后必须留空格(如 [ $a -gt 5 ],否则报错)。
  • 复杂条件可用 && 和 ||(适用于命令级逻辑,如 command1 && command2 表示 command1 成功后执行 command2)。

五、数组:批量数据处理

1. 定义数组

  • 方式 1:直接赋值(下标从 0 开始)
    fruits=("apple" "banana" "orange")  # 定义包含三个元素的数组
    
  • 方式 2:指定下标(支持稀疏数组)
    numbers[0]=10
    numbers[2]=30  # 下标 1 未定义,值为空
    
  • 方式 3:省略下标(自动递增)
    array=()
    array+=("one")  # 追加元素
    array+=("two")
    

2. 访问数组元素

  • 获取单个元素${数组名[下标]}
    echo ${fruits[1]}  # 输出:banana
    
  • 获取所有元素${数组名[@]} 或 ${数组名[*]}
    echo ${fruits[@]}  # 输出:apple banana orange
    
  • 获取数组长度${#数组名[@]}
    echo ${#fruits[@]}  # 输出:3
    
  • 切片操作(从下标 1 开始,取 2 个元素)
    echo ${fruits[@]:1:2}  # 输出:banana orange
    

3. 遍历数组示例

#!/bin/bash
nums=(1 3 5 7 9)
for num in ${nums[@]}; do  # 遍历数组所有元素echo "当前数字:$num"
done
# 输出:
# 当前数字:1
# 当前数字:3
# 当前数字:5
# 当前数字:7
# 当前数字:9

六、流程控制:脚本的 “逻辑大脑”

在 Shell 编程中,流程控制是实现复杂逻辑的核心。通过条件判断和循环结构,脚本可以根据不同场景执行不同操作,实现自动化任务。本节将从基础语法到实战案例,逐步解析 Shell 流程控制的核心知识点。

1. 条件判断:让脚本 “会思考”

1.1 if 语句:最基础的条件分支

语法格式

if [ 条件 ]; then命令1  # 条件为真时执行
elif [ 条件2 ]; then  # 可选,多个条件分支命令2
else  # 可选,所有条件不满足时执行命令3
fi  # 必须以 fi 结束

关键细节

  • 条件表达式:需用中括号 [ ] 包裹,且括号前后必须留空格(如 [ $a -gt 5 ],否则报错)。
  • 文件判断参数:常用 -e(存在)、-f(普通文件)、-d(目录)等(见下表)。
运算符含义示例
-e文件 / 目录存在[ -e /etc/passwd ] → 真
-f是普通文件[ -f script.sh ] → 真(若文件存在)
-d是目录[ -d /home ] → 真

示例:判断文件类型

#!/bin/bash
file="./test.txt"if [ -e "$file" ]; then          # 文件存在if [ -f "$file" ]; then         # 是普通文件echo "文件 $file 是普通文件"elif [ -d "$file" ]; then       # 是目录echo "文件 $file 是目录"else                            # 其他类型(如链接、设备文件)echo "文件 $file 是特殊文件"fi
elseecho "文件 $file 不存在"
fi
1.2 case 语句:模式匹配的高效选择

语法格式

case 变量 in模式1)命令1;;  # 必须用双分号结束分支模式2)命令2;;*)  # 通配符:匹配所有未定义的模式命令3;;
esac  # 必须以 esac 结束

适用场景

  • 菜单驱动程序(如用户输入 1-5 选择操作)。
  • 文件类型判断(如根据扩展名执行不同解压命令)。

示例:简易菜单程序

#!/bin/bash
echo "请选择操作(1-3):"
echo "1. 查看当前目录"
echo "2. 查看系统时间"
echo "3. 退出程序"
read choicecase $choice in1)ls -l  # 列出当前目录文件;;2)date +"%Y-%m-%d %H:%M:%S"  # 显示当前时间;;3)echo "退出程序"exit 0  # 退出脚本;;*)echo "无效选择!请输入 1-3";;
esac

2. 循环结构:让脚本 “重复执行”

2.1 for 循环:遍历列表或范围

格式 1:遍历列表(新手友好)

for 变量 in 元素1 元素2 元素3; do命令  # 对每个元素执行操作
done

示例:打印所有水果

fruits=("apple" "banana" "orange")
for fruit in ${fruits[@]}; doecho "当前水果:$fruit"
done
# 输出:
# 当前水果:apple
# 当前水果:banana
# 当前水果:orange

格式 2:C 语言风格(指定次数)

for ((初始值; 条件; 增量)); do命令  # 按次数循环
done

示例:计算 1+2+…+10

sum=0
for ((i=1; i<=10; i++)); dosum=$((sum + i))
done
echo "总和:$sum"  # 输出:55
2.2 while 循环:条件驱动的重复

语法格式

while [ 条件 ]; do命令  # 条件为真时持续执行
done

示例:逐行读取文件

#!/bin/bash
file="users.txt"
while read line; do  # 每次读取文件一行到变量 lineecho "用户:$line"
done < "$file"  # 从文件获取输入(重定向)
2.3 until 循环:反条件循环

语法格式

until [ 条件 ]; do命令  # 条件为假时持续执行,直到条件为真
done

示例:等待文件生成

until [ -e "data.csv" ]; do  # 直到文件存在echo "等待 data.csv 生成..."sleep 1  # 休眠 1 秒
done
echo "文件已生成!"

3. 循环控制:让流程更灵活

3.1 break 与 continue
关键字作用示例
break跳出当前循环(类似 C 语言)for i in 1 2 3; do if [ $i -eq 2 ]; then break; fi; done(仅打印 1)
continue跳过当前循环迭代for i in 1 2 3; do if [ $i -eq 2 ]; then continue; fi; echo $i; done(打印 1, 3)
3.2 嵌套循环:解决复杂逻辑

示例:打印乘法表

for i in {1..9}; dofor j in {1..9}; doecho -n "$i×$j=$((i*j)) "  # -n 不换行doneecho  # 换行
done

4. 函数:代码复用的 “积木”

4.1 定义与调用函数

语法格式

# 格式 1(简洁写法)
函数名() {local 变量  # 声明局部变量(仅限函数内使用)命令       # 函数逻辑return 退出码  # 可选,0 表示成功,非 0 表示失败
}# 格式 2(显式声明)
function 函数名() {命令
}

示例:计算两数之和

#!/bin/bash
# 定义函数:接收两个参数,返回和
add() {local a=$1  # 局部变量,避免污染全局作用域local b=$2echo $((a + b))  # 通过 echo 输出结果(推荐)return 0        # 返回成功状态
}# 调用函数并获取结果
result=$(add 5 3)
echo "5 + 3 = $result"  # 输出:8
echo "函数返回值:$?"    # 输出:0(成功)
4.2 函数参数传递
  • 位置参数:函数内通过 $1$2 等获取调用时传递的参数。
  • 参数验证:调用前检查参数个数,避免空指针错误。
    add() {if [ $# -ne 2 ]; then  # 检查参数个数是否为 2echo "错误:需要 2 个参数"return 1fi# 逻辑代码
    }
    

5. 常见易错点与解决方案

5.1 中括号空格缺失

错误示例

if [ $age>18 ]; then  # 错误(缺少空格)
if [ $age -gt 18 ]; then  # 正确

解决方案:始终在中括号内外留空格([ 条件 ])。

5.2 case 分支遗漏 ;;

错误示例

case $choice in1) echo "选项 1"  # 缺少 ;;,导致语法错误
esac

解决方案:每个分支必须以 ;; 结束。

5.3 无限循环陷阱

错误示例

while [ 1 -eq 1 ]; do  # 条件永远为真,导致无限循环echo "陷阱!"
done

解决方案:确保循环条件最终会变为假,或用 break 强制退出。

6. 实战案例:流程控制综合应用

6.1 文件备份脚本
#!/bin/bash
# 功能:判断目录是否存在,存在则备份,否则创建并备份backup_dir="/backup"
source_dir="/data"# 判断备份目录是否存在
if [ ! -d "$backup_dir" ]; thenmkdir -p "$backup_dir"  # 创建目录(-p 自动创建父目录)echo "创建备份目录:$backup_dir"
fi# 备份数据(使用时间戳命名备份文件)
timestamp=$(date +%Y%m%d%H%M%S)
tar -czf "$backup_dir/data_$timestamp.tar.gz" "$source_dir"echo "备份完成!文件路径:$backup_dir/data_$timestamp.tar.gz"
6.2 交互式猜数字游戏
#!/bin/bash
# 生成 1-100 随机数
num=$((RANDOM % 100 + 1))
attempts=0  # 记录尝试次数while true; do  # 无限循环,直到猜对read -p "请输入一个数字(1-100):" guessattempts=$((attempts + 1))if [ $guess -eq $num ]; thenecho "恭喜!你在 $attempts 次内猜对了!"break  # 跳出循环elif [ $guess -gt $num ]; thenecho "猜大了!再试一次。"elseecho "猜小了!再试一次。"fi
done

7. 拓展知识:让流程控制更强大

7.1 复合条件表达式
  • 逻辑与&&(如 command1 && command2,仅当 command1 成功时执行 command2)。
  • 逻辑或||(如 command1 || command2,仅当 command1 失败时执行 command2)。
7.2 函数递归

示例:计算阶乘(递归实现)

factorial() {local n=$1if [ $n -eq 0 ]; thenecho 1elseecho $((n * $(factorial $((n-1)))))fi
}result=$(factorial 5)
echo "5 的阶乘:$result"  # 输出:120
7.3 循环性能优化
  • 减少 I/O:将多次 echo 合并为一次,或使用 printf 提升效率。
  • 避免全局变量:函数内使用 local 声明变量,提高代码可读性和安全性。

8. 总结:流程控制的 “黄金法则”

  1. 条件判断:善用 if 和 case,复杂逻辑用 case 提高可读性。
  2. 循环选择:列表遍历用 for,条件驱动用 while,反向条件用 until
  3. 函数设计:参数验证、局部变量、明确返回值,提升代码复用性。
  4. 调试技巧:用 set -x 开启调试模式,查看循环和条件的执行流程。

通过掌握流程控制,你将能编写具备 “智能” 的 Shell 脚本,实现从简单任务到复杂自动

七、函数:代码复用的核心

在 Shell 编程中,函数是实现代码复用和模块化的关键。通过将重复或通用的逻辑封装为函数,不仅能减少代码冗余,还能提高脚本的可读性和维护性。本节将从函数的基础语法出发,结合实战案例,逐步解析函数的核心知识点。

1. 函数基础:从定义到调用

1.1 函数定义的两种格式

格式 1:简洁写法(推荐新手)

函数名() {命令1命令2return 退出码  # 可选,默认返回最后一条命令的状态(0-255)
}

格式 2:显式声明(清晰易读)

function 函数名() {命令
}

关键说明

  • function 关键字可选,但显式声明能提高代码可读性。
  • return 用于指定退出码(0 表示成功,非 0 表示失败),省略时返回最后一条命令的状态。

示例:定义一个打招呼函数

greet() {echo "Hello, $1!"  # $1 是函数的第一个参数return 10  # 手动设置返回码为 10
}
1.2 调用函数与参数传递

语法

函数名 参数1 参数2 参数3  # 参数之间用空格分隔

示例:调用打招呼函数

greet "Alice"  # 输出:Hello, Alice!
echo "函数返回码:$?"  # 输出:10(通过 $? 获取返回码)

2. 参数处理:让函数更灵活

2.1 位置参数:函数的 “输入变量”
变量含义示例(函数调用 add 5 3
$1第一个参数5
$2第二个参数3
$#参数个数2
$@所有参数(独立字符串)5 3

示例:计算两数之和的函数

add() {local sum=$(( $1 + $2 ))  # local 声明局部变量,避免污染全局作用域echo "和为:$sum"  # 输出结果(推荐通过 echo 返回数据)return 0  # 返回成功状态码
}# 调用函数并获取结果
result=$(add 5 3)  # 将函数输出赋值给变量
echo "计算结果:$result"  # 输出:计算结果:8
2.2 参数验证:避免无效输入

场景:当函数需要固定数量的参数时,先检查参数个数。

add() {if [ $# -ne 2 ]; then  # 检查参数是否为 2 个echo "错误:需要 2 个参数,实际 $# 个"return 1  # 返回错误码filocal sum=$(( $1 + $2 ))echo $sum
}# 调用错误示例
add 5  # 输出:错误:需要 2 个参数,实际 1 个
echo "返回码:$?"  # 输出:1

3. 变量作用域:避免 “变量污染”

3.1 全局变量:脚本内处处可见

特点:在函数外定义的变量,或函数内未用 local 声明的变量,均可在全局访问。

global_var="全局变量"show_global() {echo "函数内访问:$global_var"  # 可直接访问全局变量
}show_global  # 输出:函数内访问:全局变量
echo "函数外访问:$global_var"  # 输出:函数外访问:全局变量
3.2 局部变量:函数内的 “私有数据”

语法:用 local 关键字声明,仅在函数内有效。

function local_demo() {local local_var="局部变量"  # 局部变量echo "函数内:$local_var"
}local_demo  # 输出:函数内:局部变量
echo "函数外:$local_var"  # 输出:(空,外部无法访问)

4. 返回值:状态与数据的双重传递

4.1 返回码(状态值)
  • 用途:通过 return 声明,用于表示函数执行是否成功(0 = 成功,非 0 = 失败)。
  • 获取方式:调用后通过 $? 获取。
check_file() {if [ -e "$1" ]; thenreturn 0  # 文件存在,返回成功码elsereturn 1  # 文件不存在,返回错误码fi
}check_file "test.sh"
if [ $? -eq 0 ]; thenecho "文件存在"
elseecho "文件不存在"
fi
4.2 数据返回(推荐方式)
  • 用途:通过 echo 或 printf 输出数据,适用于返回字符串、数值等复杂结果。
  • 获取方式:用命令替换 $(函数名) 接收输出。
get_current_time() {date +"%Y-%m-%d %H:%M:%S"  # 直接输出时间
}time_now=$(get_current_time)
echo "当前时间:$time_now"  # 输出:当前时间:2023-10-01 15:30:00

5. 高级技巧:让函数更强大

5.1 函数递归:用循环逻辑解决复杂问题

场景:计算阶乘、斐波那契数列等递归问题。

# 计算 n 的阶乘(递归实现)
factorial() {local n=$1if [ $n -eq 0 ]; thenecho 1  # 递归终止条件elseecho $(( $n * $(factorial $((n-1))) ))  # 递归调用fi
}result=$(factorial 5)
echo "5 的阶乘:$result"  # 输出:120
5.2 默认参数:让函数更 “智能”

语法:通过 ${参数:-默认值} 实现参数默认值。

greet() {local name=${1:-"Guest"}  # 若未传参,默认值为 "Guest"echo "Hello, $name!"
}greet  # 输出:Hello, Guest!(未传参时用默认值)
greet "Alice"  # 输出:Hello, Alice!(传参时用实际值)
5.3 可变参数:处理不确定数量的输入

场景:函数需要接收任意数量的参数(如日志函数记录多个信息)。

log() {local timestamp=$(date +"%Y-%m-%d %H:%M:%S")echo "[${timestamp}] $*"  # $* 表示所有参数(视为单个字符串)
}log "用户登录" "IP: 192.168.1.1"  # 输出:[2023-10-01 15:30:00] 用户登录 IP: 192.168.1.1

6. 常见易错点与解决方案

6.1 忘记声明局部变量导致全局污染

错误示例

count=0  # 全局变量
increment() {count=$((count + 1))  # 未用 local,修改全局变量
}increment
echo "全局 count:$count"  # 输出:1(全局变量被修改)

解决方案:在函数内用 local 声明变量:

increment() {local count=$((count + 1))  # 局部变量,不影响全局
}
6.2 参数索引错误(如 $0 误用)

错误示例

add() {echo $0  # 输出脚本名,而非第一个参数($0 是脚本名,参数从 $1 开始)
}

解决方案:牢记函数内参数从 $1 开始,$0 是脚本名。

6.3 返回码与数据返回混淆

错误做法:用 return 返回数据(仅支持 0-255 的整数)。

add() {return $((5 + 3))  # 错误,return 只能返回状态码
}

正确做法:用 echo 输出数据,return 仅用于状态码。

7. 实战案例:函数综合应用

7.1 文件操作函数库

需求:封装常用文件操作函数,如创建目录、复制文件。

#!/bin/bash# 函数 1:创建目录(带错误处理)
create_dir() {local dir=$1if [ -d "$dir" ]; thenecho "目录 $dir 已存在"return 1fimkdir -p "$dir"if [ $? -eq 0 ]; thenecho "目录 $dir 创建成功"return 0elseecho "目录 $dir 创建失败"return 1fi
}# 函数 2:复制文件到目录
copy_file() {local src=$1local dest_dir=$2if [ ! -f "$src" ]; thenecho "源文件 $src 不存在"return 1fiif [ ! -d "$dest_dir" ]; thencreate_dir "$dest_dir"  # 调用其他函数if [ $? -ne 0 ]; thenreturn 1fificp "$src" "$dest_dir"echo "文件 $src 复制到 $dest_dir 成功"return 0
}# 调用函数
copy_file "data.txt" "backup"
7.2 交互式菜单函数

需求:通过函数实现菜单驱动的用户交互。

show_menu() {echo "===== 菜单 ====="echo "1. 查看系统信息"echo "2. 退出程序"echo "================"
}handle_choice() {local choice=$1case $choice in1)uname -a  # 显示系统信息;;2)echo "退出程序"exit 0;;*)echo "无效选择!";;esac
}# 主程序
while true; doshow_menu  # 调用菜单函数read -p "请选择:" choicehandle_choice "$choice"  # 调用选择处理函数
done

8. 拓展知识:函数的进阶应用

8.1 函数库管理
  • 创建函数文件:将常用函数保存到独立文件(如 utils.sh)。
    # utils.sh 内容
    function add() { ... }
    function greet() { ... }
    
  • 引入函数库:通过 source 命令在脚本中引用。
    source utils.sh  # 使 utils.sh 中的函数在当前脚本生效
    add 5 3  # 直接调用
    
8.2 函数调试技巧
  • 开启调试模式:用 set -x 跟踪函数执行步骤。
    set -x  # 开启调试
    add 5 3  # 显示每一步执行的命令
    set +x  # 关闭调试
    
  • 打印参数信息:在函数开头输出参数,确认输入是否正确。
    add() {echo "接收到的参数:$1, $2"  # 调试用输出...
    }
    

9. 总结:函数的 “复用哲学”

  • 代码复用:将重复逻辑封装为函数,避免 “重复造轮子”。
  • 模块化设计:每个函数专注于一个独立功能(如文件操作、数据计算),提高可维护性。
  • 错误处理:通过参数验证和返回码,让函数更健壮。

掌握函数后,你将从 “编写零散命令” 进阶到 “构建结构化脚本”。建议从简单函数开始,逐步积累常用工具函

八、实战案例:判断闰年

需求

输入年份,判断是否为闰年(闰年条件:能被 4 整除且不能被 100 整除,或能被 400 整除)。

脚本实现

#!/bin/bash
read -p "请输入年份:" year# 组合条件:(year%400==0) 或 (year%4==0 且 year%100!=0)
if [ $((year % 400)) -eq 0 ] || [ \( $((year % 4)) -eq 0 -a $((year % 100)) -ne 0 \) ]; thenecho "$year 是闰年"
elseecho "$year 是平年"
fi

关键点

  • \(` 和 `\) 用于转义括号,确保条件正确组合。
  • || 表示逻辑或,-a 表示逻辑与。

九、常见易错点总结

  1. 变量赋值空格a = 10 错误,必须为 a=10(等号前后不能有空格)。
  2. 中括号空格[条件] 需写成 [ 条件 ],否则报错(如 [a -gt 5] 错误,应为 [ $a -gt 5 ])。
  3. 文件路径错误:执行脚本时需用 ./脚本名,直接输入 脚本名 会提示 “命令未找到”。
  4. 转义符遗漏:使用 expr 计算乘法时,* 需转义为 \*,或改用 $(( )) 避免转义。
  5. 字符串比较误区:比较字符串是否相等时,= 前后需留空格(如 [ "$a" = "$b" ]),否则会被视为赋值。

十、总结

Shell 编程是 Linux 自动化的核心技能,从简单的脚本到复杂的流程控制,需要通过大量实践掌握。新手入门时,建议:

  1. 从单个知识点入手,如变量、循环、函数,逐个击破。
  2. 每学一个语法,编写小例子验证效果,理解背后逻辑。
  3. 遇到错误时,善用 echo 打印变量值,或用 bash -x 脚本名 调试(显示每行执行过程)。

记住:Shell 脚本的魅力在于 “用简单命令组合实现强大功能”,坚持练习,你会逐渐体会到它的高效与便捷!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/901496.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用shell脚本实现自动监控并封禁连接数超过阈值的IP

写一个 shell 脚本&#xff0c;创建脚本文件 /usr/local/bin/check_conn.sh #!/bin/bash if [[ $EUID -ne 0 ]]; thenecho "This script must be run as root." >&2exit 1 fi # 连接数阈值 THRESHOLD50# 白名单 IP&#xff08;空格分隔&#xff09; WHITELIS…

VS 中Git 中本地提交完成,没有推送,修改的内容如何还原

在 Visual Studio 中撤销本地提交但未推送的修改&#xff0c;可以通过以下方法实现&#xff1a; 一、保留修改内容&#xff08;仅撤销提交记录&#xff09; 使用 git reset --soft 在 VS 的 Git 终端中执行&#xff1a; git reset --soft HEAD~1作用&#xff1a;撤销最后一次提…

qt中的正则表达式

问题&#xff1a; 1.在文本中把dog替换成cat&#xff0c;但可能会把dog1替换成cat1&#xff0c;如果原本不想替换dog1&#xff0c;就会出现问题 2文本中想获取某种以.txt为结尾的多有文本&#xff0c;普通的不能使用 3如果需要找到在不同的系统中寻找换行符&#xff0c;可以…

Linux命令-vim编辑

用vi或vim命令进入vim编辑器。 基础: u 撤销上一次操作。x剪切当前光标所在处的字符。yy复制当前行。dd剪切当前行。p粘贴剪贴板内容到光标下方。i切换到输入模式&#xff0c;在光标当前位置开始输入文本。:wq保存并退出Vim 编辑器。:q!不保存强制退出Vim 编辑器。 拓展: w光…

VS 基于git工程编译版本自动添加版本号

目录 概要 实现方案 概要 最近在用visual Studio 开发MFC项目时&#xff0c;需要在release版本编译后的exe文件自动追加版本信息。 由于我们用的git工程管理&#xff0c;即需要基于最新的git 提交来打版本。 比如&#xff1a; MFCApplication_V1.0.2_9.exe 由于git 提交信…

nginx入门,部署静态资源,反向代理,负载均衡使用

Nginx在linux上部署静态资源 概念介绍 Nginx可以作为静态web服务器来部署静态资源。这里所说的静态资源是指在服务端真实存在&#xff0c;并且能够直接展示的一些文件&#xff0c;比如常见的html页面、css文件、js文件、图片、视频等资源。 相对于Tomcat&#xff0c;Nginx处理…

【字节跳动AI论文】Seaweed-7B:视频生成基础模型的高成本效益培训

摘要&#xff1a;本技术报告介绍了一种经济有效的视频生成基础模型训练策略。 我们提出了一种中等规模的研究模型&#xff0c;大约有70亿个参数&#xff08;7B&#xff09;&#xff0c;称为Seaweed-7B&#xff0c;使用665,000个H100 GPU小时从头开始训练。 尽管使用适度的计算资…

Java单例模式:实现全局唯一对象的艺术

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、什么是单例模式&#xff1f; 单例模式&#xff08;Singleton Pattern&#xff09;是一种创建型设计模式&#xff0c;确保一个类只有一个实例&#xff0c…

Oracle 复制表结构(含索引、主键)操作指南

Oracle 复制表结构&#xff08;含索引、主键&#xff09;操作指南 1. 复制基础表结构 -- 创建空表结构&#xff08;不复制数据&#xff09; CREATE TABLE new_table AS SELECT * FROM old_table WHERE 10;2. 复制主键约束 -- 查询原表主键信息 SELECT constraint_name, co…

React 更新state中的对象

更新 state 中的对象 state 中可以保存任意类型的 JavaScript 值&#xff0c;包括对象。但是&#xff0c;你不应该直接修改存放在 React state 中的对象。相反&#xff0c;当你想要更新一个对象时&#xff0c;你需要创建一个新的对象&#xff08;或者将其拷贝一份&#xff09;…

基于 GoFrame 框架的电子邮件发送实践:优势、特色与经验分享

1. 引言 如果你是一位有1-2年Go开发经验的后端开发者&#xff0c;可能已经熟悉了Go语言在性能和并发上的天然优势&#xff0c;也曾在项目中遇到过邮件发送的需求——无论是用户注册时的激活邮件、系统异常时的通知&#xff0c;还是营销活动中的批量促销邮件&#xff0c;邮件功…

AndroidStudio编译报错 Duplicate class kotlin

具体的编译报错信息如下&#xff1a; Duplicate class kotlin.collections.jdk8.CollectionsJDK8Kt found in modules kotlin-stdlib-1.8.10 (org.jetbrains.kotlin:kotlin-stdlib:1.8.10) and kotlin-stdlib-jdk8-1.6.21 (org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.21) D…

后端面试问题收集以及答案精简版

思路 不要问什么答什么 要学会扩充 比如问你go map的原理 map 是什么 数据结构&#xff0c;字典&#xff0c;k/v 结构map的应用场景有哪些 快速查找、计数器、配置管理、去重、缓存实现map有哪些限制 无序性、非线程安全的读写map的key的访问 v: mp[key] v,ok : mp[key] for…

MicroPython 开发ESP32应用教程 之 I2S、INMP441音频录制、MAX98357A音频播放、SD卡读写

本课程我们讲解Micropython for ESP32 的i2s及其应用&#xff0c;比如INMP441音频录制、MAX98357A音频播放等&#xff0c;还有SD卡的读写。 一、硬件准备 1、支持micropython的ESP32S3开发板 2、INMP441数字全向麦克风模块 3、MAX98357A音频播放模块 4、SD卡模块 5、面包板及…

UE5 物理模拟 与 触发检测

文章目录 碰撞条件开启模拟关闭模拟 多层级的MeshUE的BUG 触发触发条件 碰撞 条件 1必须有网格体组件 2网格体组件必须有网格&#xff0c;没有网格虽然可以开启物理模拟&#xff0c;但是不会有任何效果 注意开启的模拟的网格体组件会计算自己和所有子网格的mesh范围 3只有网格…

微信小程序 - swiper轮播图

官方文档&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/component/swiper.html <swiper indicator-color"ivory" indicator-active-color"#d43c33" indicator-dots autoplay><swiper-item><image src"/images/banner…

深入探究C#官方MCP:开启AI集成新时代

一、引言 在当今数字化时代&#xff0c;.NET 开发领域不断演进&#xff0c;而 C# 官方 MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;的出现&#xff0c;无疑为开发者们带来了全新的机遇与挑战。随着人工智能技术的迅猛发展&#xff0c;将 AI…

二分查找法

使用二分查找法的前提&#xff1a;&#xff08;1&#xff09;数组为有序数组. &#xff08;2&#xff09;数组中无重复元素. 二分的两种写法&#xff1a; 方法一&#xff1a;[left&#xff0c;right] class Solution { public:int search(vector<int>& nums, int …

HarmonyOS:页面滚动时标题悬浮、背景渐变

一、需求场景 进入到app首页或者分页列表首页时&#xff0c;随着页面滚动&#xff0c;分类tab要求固定悬浮在顶部。进入到app首页、者分页列表首页、商品详情页时&#xff0c;页面滚动时&#xff0c;顶部导航栏&#xff08;菜单、标题&#xff09;背景渐变。 二、相关技术知识点…

鲲鹏+昇腾部署集群管理软件GPUStack,两台服务器搭建双节点集群【实战详细踩坑篇】

前期说明 配置&#xff1a;2台鲲鹏32C2 2Atlas300I duo&#xff0c;之前看网上文档&#xff0c;目前GPUstack只支持910B芯片&#xff0c;想尝试一下能不能310P也部署试试&#xff0c;毕竟华为的集群软件要收费。 系统&#xff1a;openEuler22.03-LTS 驱动&#xff1a;24.1.rc…