Bash 脚本编程入门教程
Bash 脚本编程:程序员的“偷懒”艺术
欢迎来到本系列教程的最后一部分。在这里,我们将一起探索一个强大的工具——脚本编程。
本篇教程旨在为你提供一个 Bash 脚本的快速入门,让你掌握足以应对日常工作的实用技能。内容整合了前面章节的许多知识点,如果你对某些命令感到陌生,不妨回顾一下之前的章节。
究竟什么是脚本?
在计算机领域,“脚本”(Script)与戏剧中的“剧本”非常相似。剧本规定了演员的台词和动作;而 Bash 脚本则是一份写给计算机的“剧本”,它包含了一系列需要执行的命令。
通过编写脚本,我们可以让计算机自动完成一系列操作,而无需我们手动逐条输入命令。这对于处理频繁或重复性的任务特别有用。
Bash 脚本由一个叫做“解释器”(Interpreter)的程序来读取和执行。Linux 系统中有多种解释器,但我们学习的是 Bash Shell,所以这里我们专注于 Bash 脚本。
请记住一个核心原则:
任何你能在命令行中运行的命令,都可以放进脚本里,其行为完全一致。反之亦然,脚本中的任何命令,也可以直接在命令行中运行,效果也完全相同。
理解这一点至关重要。当你在构建脚本、测试某个部分的功能时,最简单的方法就是直接在命令行里运行对应的命令。
脚本本质上只是一个纯文本文件,你可以用任何文本编辑器(例如我们学过的 vi
)来创建它。
一个简单的例子
让我们从一个简单的脚本开始。我建议你亲手创建这个文件并运行它,以感受其工作方式。这个脚本会先在屏幕上打印一条消息,然后列出当前目录下的所有文件。
1. 创建脚本文件 myscript.sh
#!/bin/bash
# 这是一个简单的演示脚本
# 作者: Ryan, 日期: 2025-09-04
echo "以下是您当前目录中的文件:"
ls
2. 赋予脚本执行权限
一个脚本文件必须拥有“执行权限”才能运行。我们使用 chmod
命令来添加权限。
chmod 755 myscript.sh
755
是一个常用的权限设置,表示文件所有者拥有读、写、执行权限,而其他用户只有读和执行权限。
3. 运行脚本
./myscript.sh
4. 查看输出
运行后,你将看到类似下面的输出:
以下是您当前目录中的文件:
barry.txt bob example.png firstfile foo1 myoutput video.mpeg
脚本构成要素详解
上面的例子虽然简单,但包含了很多关键点。让我们来逐一解析。
1. Shebang (#!
)
脚本的第一行必须是 #!/bin/bash
。
这一行被称为 Shebang,它的作用是告诉系统应该使用哪个解释器来执行这个文件。
#!
是固定格式,必须出现在文件的最开头,前面不能有任何空格或空行。/bin/bash
是 Bash 解释器的路径。
如果你不确定解释器的路径,可以使用 which
命令查找:
$ which bash
/bin/bash
$ which ls
/usr/bin/ls
虽然省略 Shebang 有时也能正常运行(因为当前 Shell 可能会默认自己为解释器),但为了保证脚本在任何环境下都能被正确解释,强烈建议始终添加 Shebang。
2. 脚本命名
Linux 是一个“无扩展名”的系统,脚本文件的名称不会影响其运行。虽然我们通常会给 Bash 脚本加上 .sh
后缀以方便识别,但这纯粹是习惯,并非强制要求。你可以将脚本命名为 myscript
甚至 myscript.jpg
,它依然能正常运行。
3. 注释 (#
)
在脚本中,以 #
开头的任何内容都会被解释器忽略,它们是写给人看的注释。
# 这是一个占据整行的注释
ls -l # 这是跟在命令后面的注释
一个好的编程习惯是在脚本开头写明脚本的功能、作者和创建日期,并在代码中对复杂的或关键的步骤添加注释。
4. 为什么需要 ./
来执行?
你可能注意到我们使用 ./myscript.sh
而不是直接用 myscript.sh
来运行脚本。这其实是 Linux 的一种安全机制。
当你输入一个命令(如 ls
)时,系统并不会搜索整个硬盘,而是会查找一个名为 PATH
的环境变量所定义的目录列表。
你可以用 echo
查看你的 PATH
变量:
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/games
系统会依次在这些目录(以冒号分隔)中寻找你输入的命令。它不会默认在你的当前目录中寻找。
通过提供路径(./
),我们明确地告诉系统:“不要去 PATH
里找了,就在我指定的这个位置执行文件”。其中:
.
代表当前目录。./myscript.sh
就表示“当前目录下的myscript.sh
文件”。
这种机制可以防止恶意行为。例如,如果有人在一个公共目录里创建了一个名为 ls
的恶意脚本,而你恰好在该目录想用 ls
命令查看文件,如果没有 ./
的保护机制,你可能会无意中运行那个恶意脚本。
核心利器:变量
变量是用来存储数据的容器,在脚本中非常有用。
基本变量
- 定义变量:
变量名=值
(注意:等号两边不能有空格) - 使用变量:
$变量名
# 脚本: variable_example.sh
#!/bin/bash
# 变量使用示例
name='Ryan'
echo "你好, $name"
运行 ./variable_example.sh
会输出 你好, Ryan
。
特殊变量 (预设变量)
运行脚本时,系统会自动为我们设置一些特殊的变量:
$0
:脚本本身的名称。$1
-$9
:传递给脚本的命令行参数。$1
是第一个参数,$2
是第二个,以此类推。$#
:传递给脚本的命令行参数的总个数。$*
:所有命令行参数组成的单个字符串。
示例脚本 arguments.sh
:
#!/bin/bash
# 特殊变量演示
echo "脚本名称是: $0"
echo "共传入了 $# 个参数"
echo "所有参数是: $*"
echo "第二个参数是: $2"
运行与输出:
$ ./arguments.sh bob fred sally
脚本名称是: ./arguments.sh
共传入了 3 个参数
所有参数是: bob fred sally
第二个参数是: fred
命令替换 (反引号或 $()
)
我们可以将一个命令的输出结果存入一个变量。这可以通过反引号 (`
) 或更现代的 $(...)
语法实现。
示例脚本 backticks.sh
:
#!/bin/bash
# 命令替换演示
# 统计 testfile.txt 文件的行数并存入变量 lines
# $1 代表传入的第一个参数,这里即 'testfile.txt'
lines=`cat $1 | wc -l`
# 推荐使用 $(...) 写法,更清晰且支持嵌套: lines=$(wc -l < $1)
echo "文件 $1 中共有 $lines 行。"
运行与输出: (假设 testfile.txt
有12行)
$ ./backticks.sh testfile.txt
文件 testfile.txt 中共有 12 行。
实战演练:一个简单的备份脚本
现在,让我们把所学知识应用到一个有用的脚本中。假设我需要定期备份 ~/projects
目录下的某个项目文件夹到 ~/projectbackups
目录。
#!/bin/bash
# 备份单个项目目录
# 作者: Ryan, 日期: 2025-09-04
# 格式化当前日期为 YYYY-MM-DD
todays_date=$(date +%F)
# 创建备份目录,格式为:项目名_日期
mkdir ~/projectbackups/$1_$todays_date
# 递归复制项目目录到备份目录
cp -R ~/projects/$1 ~/projectbackups/$1_$todays_date
echo "项目 $1 的备份已完成!"
运行与输出:
$ ./projectbackup.sh ocelot
项目 ocelot 的备份已完成!
这个脚本通过使用 $1
作为项目名,变得非常通用。我的同事也可以直接使用它来备份他们自己的项目,而无需修改脚本代码。编写灵活、通用的脚本能极大地提高效率。
让脚本更智能:If 条件判断
上面的备份脚本虽然能用,但如果使用者输错了参数,或者目录不存在,脚本就会报错。我们可以使用 if
语句来增加判断逻辑,让脚本更健壮。
(这部分内容相对进阶,如果觉得困惑可以先跳过。即使只掌握以上知识,也足以编写出许多实用的脚本。)
增强版备份脚本 projectbackup_v2.sh
:
#!/bin/bash
# 更健壮的项目备份脚本
# 作者: Ryan, 日期: 2025-09-04
# 检查参数数量是否不为1
if [ $# != 1 ]; then
echo "用法: $0 <要备份的目录名>"
exit 1
fi
# 检查源项目目录是否存在
if [ ! -d ~/projects/$1 ]; then
echo "错误: 目录 ~/projects/$1 不存在,请检查拼写。"
exit 1
fi
todays_date=$(date +%F)
backup_dir=~/projectbackups/$1_$todays_date
# 检查今天是否已经备份过
if [ -d $backup_dir ]; then
echo "今天似乎已经备份过该项目,是否覆盖? (y/n)"
read answer # 读取用户输入
if [ "$answer" != "y" ]; then
echo "操作取消。"
exit 0
fi
else
mkdir -p $backup_dir # 如果不存在,则创建
fi
cp -R ~/projects/$1 $backup_dir
echo "项目 $1 的备份已完成!"
代码解析:
if [ ... ]; then ... fi
是if
语句的基本结构。fi
是if
的倒写,表示条件块结束。[ $# != 1 ]
:[
是test
命令的简写。$#
是参数个数,!=
是不等于。这行代码判断“参数个数是否不等于1”。注意:
[
和]
与其中的表达式之间必须有空格!exit
: 立即退出脚本。通常exit 0
表示成功,非0值表示错误。[ ! -d ~/projects/$1 ]
:-d
用来测试一个路径是否存在且为目录,!
表示“非”(not)。所以这行代码判断“如果~/projects/$1
不是一个目录”。read answer
: 读取用户的键盘输入,并将其存入answer
变量。
为了代码的可读性,if
块内的代码通常会进行缩进,虽然这不是强制的,但这是一个非常好的编程习惯。
知识点总结
#!
: Shebang。指定脚本的解释器。echo
: 在屏幕上打印消息。which
: 查找一个命令的完整路径。$
: 引用变量值的前缀。``
或$( )
: 命令替换。用于将命令的输出赋值给变量。date
: 显示或格式化日期时间。if [ ] then else fi
: 实现基本的条件逻辑判断。- 核心原则
: 命令行能做的,脚本就能做,反之亦然。 - 格式要求
: Bash 脚本对空格非常敏感,尤其是在变量赋值和if
判断中,要特别注意。
课后练习
现在,运用你学到的所有知识,来自动化一些任务吧!
- 编写你自己的备份脚本。可以从一个最简单的版本开始,然后逐步增加功能,比如添加日期、检查目录是否存在、压缩备份文件等。
- 编写一个目录报告脚本。这个脚本接受一个目录名作为参数,然后输出关于该目录的报告,可以包括:
- 目录中有多少个文件?
- 有多少个子目录?
- 哪个文件最大?
- 哪个文件是最新修改的?
- 统计目录中文件分属哪些不同用户。
- 任何你能想到的其他信息...