对于刚接触 Linux 或 Unix 系统的开发者来说,Shell 脚本往往是自动化操作的第一道门槛。它不像 Python 那样语法简洁,也不像 Java 那样有完善的面向对象体系,但却能以极少的代码实现强大的系统管理功能。本文将从 Shell 的基本概念讲起,带你一步步掌握脚本编写的核心基础,完成从 “手动操作” 到 “自动化脚本” 的跨越。
一、认识 Shell:什么是 Shell,为什么需要它?
简单来说,Shell 是用户与操作系统内核之间的 “翻译官”—— 它接收用户输入的命令,传递给内核执行,并将结果返回给用户。我们在终端中输入的ls、cd、mkdir等命令,都是通过 Shell 解析执行的。
常见的 Shell 类型
不同的操作系统默认搭载的 Shell 可能不同,常见的有:
- bash:Linux 系统中最常用的 Shell,兼容 sh,支持丰富的扩展功能(如命令补全、历史记录);
- zsh:功能更强大的 Shell,支持更多主题和插件,是很多开发者的个性化选择;
- sh:早期的 Bourne Shell,语法简单但功能有限,现在多被 bash 兼容替代。
在终端中输入echo $SHELL可以查看当前使用的 Shell,本文将以最通用的bash为例进行讲解。
为什么要学 Shell 编程?
想象以下场景:
- 每天需要手动备份 10 个目录的日志文件;
- 批量修改 100 个文件的命名格式;
- 定期检查服务器的磁盘使用率并发送告警。
这些重复劳动如果用 Shell 脚本实现自动化,能节省大量时间。Shell 脚本的核心价值在于 “批量执行命令 + 逻辑控制”,它能将一系列命令按顺序组织,并通过条件判断、循环等逻辑实现复杂任务。
二、第一个 Shell 脚本:Hello World 的诞生
编写 Shell 脚本的流程非常简单,只需三个步骤:创建文件→编写代码→赋予权限并执行。
1. 创建脚本文件
用任意文本编辑器(如 vim、nano)创建一个.sh后缀的文件,例如hello.sh:
vim hello.sh
2. 编写脚本内容
在文件中输入以下代码:
#!/bin/bash
# 这是我的第一个Shell脚本
echo "Hello, Shell Programming!"
代码解析:
- #!/bin/bash:称为 “shebang”,必须放在脚本第一行,指定脚本由 bash 解释执行;
- # 这是注释:#开头的行是注释,用于说明代码功能,不被执行;
- echo "...":打印字符串到终端,类似其他语言的print函数。
3. 赋予执行权限并运行
刚创建的脚本默认没有执行权限,需要用chmod命令赋予:
chmod +x hello.sh # 赋予执行权限
然后执行脚本:
./hello.sh # 输出:Hello, Shell Programming!
也可以直接通过 bash 命令运行(无需执行权限):
bash hello.sh # 同样生效
小技巧:如果脚本放在/usr/local/bin等环境变量目录下,可直接输入文件名执行(如hello.sh),无需带路径。
三、Shell 变量:存储数据的容器
和其他编程语言一样,Shell 也需要变量来存储数据。但与 Python 等语言不同,Shell 变量的定义和使用有其独特性。
1. 变量的定义与赋值
定义变量的格式为变量名=值,注意等号两边不能有空格:
name="Alice" # 正确:字符串赋值
age=25 # 正确:数字赋值
# 错误写法:name = "Bob"(等号两边有空格)
2. 变量的使用
使用变量时需在变量名前加$符号,例如:
#!/bin/bash
name="Alice"
echo "My name is $name" # 输出:My name is Alice
echo "Age: $age" # 输出:Age: (未定义的变量会被视为空)
如果变量名与其他字符相邻,可用大括号{}区分:
echo "Hello, ${name}123" # 输出:Hello, Alice123(若不加{}会被解析为$name123)
3. 环境变量与局部变量
- 局部变量:仅在当前脚本或终端中有效,如上面定义的name;
- 环境变量:全局生效,可被所有子进程访问,常用的有:
-
- $HOME:用户主目录(如/home/alice);
-
- $PATH:命令搜索路径(存放可执行命令的目录);
-
- $USER:当前登录用户名;
-
- $?:上一条命令的退出状态(0 表示成功,非 0 表示失败)。
查看环境变量可用echo,例如:
echo $HOME # 输出:/home/alice
echo $PATH # 输出:/usr/local/sbin:/usr/local/bin:...
自定义环境变量需用export命令:
export WORK_DIR="/data/project" # 定义环境变量
四、数据类型:字符串与数字的处理
Shell 是弱类型语言,变量默认都按字符串处理,但也支持数字运算。
1. 字符串操作
字符串是 Shell 中最常用的数据类型,支持拼接、截取、替换等操作。
(1)字符串拼接
直接将变量或字符串放在一起即可:
first="Hello"
last="World"
full="$first $last" # 拼接为"Hello World"
echo $full # 输出:Hello World
(2)字符串长度
用${#变量名}获取长度:
str="Shell"
echo ${#str} # 输出:5("Shell"有5个字符)
(3)字符串截取
格式:${变量名:起始位置:长度}(起始位置从 0 开始):
path="/home/user/docs/file.txt"
# 从第6个字符开始截取(跳过"/home/")
echo ${path:6} # 输出:user/docs/file.txt
# 从第6个字符开始,截取4个字符
echo ${path:6:4} # 输出:user
(4)字符串替换
- 替换第一个匹配项:${变量名/旧字符串/新字符串}
- 替换所有匹配项:${变量名//旧字符串/新字符串}
示例:
filename="report_2023_v1.txt"
# 替换第一个"_"为"-"
echo ${filename/_/-} # 输出:report-2023_v1.txt
# 替换所有"_"为"-"
echo ${filename//_/-} # 输出:report-2023-v1.txt
2. 数字运算
Shell 默认将数字视为字符串,需用特殊语法进行运算,常用的有两种方式:
(1)$((表达式))
适合简单运算:
a=10
b=3
echo $((a + b)) # 输出:13
echo $((a * b)) # 输出:30
echo $((a / b)) # 输出:3(整数除法,向下取整)
echo $((a % b)) # 输出:1(取余数)
(2)expr 命令
注意表达式中运算符两边需有空格:
expr 10 + 3 # 输出:13
expr 10 \* 3 # 乘法需加转义符\(避免被Shell解析)
推荐使用$((...)),语法更简洁且支持变量直接参与运算。
五、输入输出与管道:数据的流转
Shell 脚本的输入输出控制和管道操作,是实现命令协作的核心。
1. 标准输入输出
默认情况下:
- 标准输入(stdin):从键盘接收输入,文件描述符为 0;
- 标准输出(stdout):输出到终端,文件描述符为 1;
- 标准错误(stderr):错误信息输出到终端,文件描述符为 2。
2. 重定向:改变输入输出的方向
通过重定向符号可将输入输出指向文件:
- >:覆盖写入文件(如echo "test" > file.txt);
- >>:追加写入文件(如echo "test" >> file.txt);
- <:从文件读取输入(如read var < file.txt);
- 2>:将错误信息写入文件(如ls error_dir 2> err.log);
- &>:将标准输出和错误都写入文件(如command &> output.log)。
示例:
# 将命令输出写入文件(覆盖原有内容)
ls -l > file_list.txt
# 追加输出到文件
echo "新内容" >> file_list.txt
# 捕获错误信息
rm non_exist_file 2> error.log
3. 管道:命令间的数据传递
管道符号|可将前一个命令的输出作为后一个命令的输入,实现命令协作:
# 查找包含"error"的日志行,并统计数量
cat app.log | grep "error" | wc -l
# 列出当前目录文件,按大小排序(逆序)
ls -l | sort -k5,5nr
命令解析:
- grep "error":筛选包含指定字符串的行;
- wc -l:统计行数;
- sort -k5,5nr:按第 5 列(文件大小)逆序(nr)排序。
六、实战案例:批量文件重命名脚本
掌握了基础语法后,我们来编写一个实用脚本:将指定目录下的所有.txt文件添加前缀(如2023_)。
脚本代码
#!/bin/bash
# 批量给txt文件添加前缀
# 使用方法:./rename_txt.sh 目录路径 前缀
# 检查参数是否正确
if [ $# -ne 2 ]; then
echo "使用错误!正确用法:$0 目录路径 前缀"
exit 1
fi
dir=$1
prefix=$2
# 检查目录是否存在
if [ ! -d "$dir" ]; then
echo "错误:目录 $dir 不存在!"
exit 1
fi
# 切换到目标目录
cd "$dir" || exit 1
# 遍历txt文件并添加前缀
for file in *.txt; do
# 跳过非文件(如目录)
if [ -f "$file" ]; then
new_name="${prefix}${file}"
mv "$file" "$new_name"
echo "已重命名:$file → $new_name"
fi
done
echo "批量重命名完成!"
代码解析
- $#表示参数个数,$0表示脚本名,$1、$2表示第一个和第二个参数;
- [ -d "$dir" ]判断是否为目录,[ -f "$file" ]判断是否为文件(条件判断语法将在后续文章详细讲解);
- for file in *.txt遍历所有 txt 文件,mv命令执行重命名。
运行效果
# 创建测试文件
mkdir test_dir
touch test_dir/{a,b,c}.txt
# 执行脚本
./rename_txt.sh test_dir 2023_
# 输出结果
已重命名:a.txt → 2023_a.txt
已重命名:b.txt → 2023_b.txt
已重命名:c.txt → 2023_c.txt
批量重命名完成!
七、总结
- Shell 的作用与常见类型;
- 脚本的创建、执行流程;
- 变量定义、数据类型(字符串、数字);
- 输入输出重定向与管道操作;
- 一个实战案例(批量重命名文件)。