第一个 Bash 脚本:一键编译运行 Hello World
一、我们之前怎么做?
之前写好了hello.c后,操作过程是:
gcc hello.c-ohello ./hello每次修改代码后,都要重复敲这两条命令。
现在我们把它写进一个脚本,以后只要一条命令就能自动完成编译 + 运行。
二、什么是 Bash 脚本?
Bash 脚本就是一个纯文本文件,里面按顺序写着终端命令。
运行这个脚本,就相当于把你手动敲的命令自动执行一遍。
类比:
- 你手动编译运行:自己一步步操作
- Bash 脚本:把步骤写成“剧本”,由计算机自动表演
优点:
- 避免重复输入,提高效率
- 可以加入判断、循环,让操作更智能
- 方便分享给他人,保证操作一致性
三、准备工作:确认你的第一个 C 程序
我们在家目录下新建一个工作目录,并把之前的hello.c放进去(如果还没有,现在就创建一个):
mkdir-p~/my_first_scriptcd~/my_first_scriptnanohello.chello.c内容(最简单的 Hello World):
#include<stdio.h>intmain(){printf("Hello, Linux!\n");return0;}保存退出。我们先手动编译运行一次,确保一切正常:
gcc hello.c-ohello ./hello看到Hello, Linux!输出,说明程序没问题。
四、编写第一个脚本build_run.sh
用编辑器创建脚本文件:
nanobuild_run.sh输入以下内容:
#!/bin/bash# ============================================# 脚本: build_run.sh# 功能: 一键编译 hello.c 并运行生成的可执行文件# ============================================SOURCE="hello.c"# 源文件TARGET="hello"# 可执行文件名echo"正在编译$SOURCE..."gcc-g-O0-o"$TARGET""$SOURCE"# 检查编译是否成功($? 是上一条命令的退出码,0 表示成功)if[$?-eq0];thenecho"编译成功!正在运行$TARGET..."echo"=============================="./"$TARGET"elseecho"编译失败,请检查代码。"exit1fi保存并退出(Ctrl+O,回车,Ctrl+X)。
五、逐行讲解脚本的每一部分
| 行 | 解释 |
|---|---|
#!/bin/bash | Shebang,告诉系统用/bin/bash解释器执行本脚本。必须是第一行。 |
# ... | 以#开头是注释,给人看的,不执行。 |
SOURCE="hello.c" | 定义变量SOURCE,值为hello.c。等号两边不能有空格。 |
TARGET="hello" | 定义变量TARGET,值为可执行文件名。 |
echo "正在编译 $SOURCE ..." | echo打印信息。$SOURCE会被替换成hello.c。 |
gcc -g -O0 -o "$TARGET" "$SOURCE" | 编译命令:-g包含调试信息,-O0不优化。变量用双引号括起来防止意外。 |
if [ $? -eq 0 ]; then | $?是上一条命令的退出码。-eq 0判断是否等于 0。整句意思是“如果编译成功,那么…” |
echo "编译成功!..." | 提示编译成功。 |
echo "==============================" | 装饰性分隔线。 |
./"$TARGET" | 运行可执行文件。 |
else | 否则(编译失败)执行下面部分。 |
echo "编译失败..." | 提示失败。 |
exit 1 | 脚本以非 0 状态退出,表示异常终止。 |
六、赋予脚本执行权限
Linux 下新建文件默认没有执行权限,需要手动添加:
chmod+x build_run.sh+x表示添加执行权限。可以用ls -l检查,应该看到类似-rwxr-xr-x的权限。
七、运行脚本
直接在当前目录执行:
./build_run.sh你会看到:
正在编译 hello.c ... 编译成功!正在运行 hello ... ============================== Hello, Linux!完美!以后修改hello.c后,只需再运行一次./build_run.sh即可,无需手动敲两条命令。
八、让脚本更通用:支持任意.c文件
目前的脚本写死了文件名。我们可以改进一下,让它能从命令行接收源文件名。
创建build.sh(通用版):
#!/bin/bash# 通用编译运行脚本# 用法: ./build.sh <源文件.c>if[$#-eq0];thenecho"用法:$0<源文件.c>"exit1fiSRC="$1"OUT="${SRC%.c}"# 去掉 .c 后缀作为可执行文件名echo"编译$SRC->$OUT..."gcc-g-O0-o"$OUT""$SRC"if[$?-eq0];thenecho"编译成功,运行 ./$OUT..."./"$OUT"elseecho"编译失败"exit1fi关键点解释:
$#:传递给脚本的参数个数。$0:脚本自己的名字。$1:第一个参数(例如hello.c)。${SRC%.c}:字符串处理,去掉末尾的.c。若SRC=hello.c,则OUT=hello。
使用示例:
chmod+x build.sh ./build.sh hello.c这就可以编译任何单个 C 文件,只要它不依赖其他库。
九、更完善的脚本:加入自动化测试
我们还可以把之前学过的diff结合进来,让脚本自动检查输出是否正确。
思路:
- 编译程序。
- 用事先准备好的输入(如果程序需要输入)和预期输出文件。
- 运行程序并将输出保存到临时文件。
- 用
diff比较实际输出和预期输出。 - 显示测试结果。
示例脚本test.sh(假设程序不需要输入,直接比输出):
#!/bin/bashgcc hello.c-ohello ./hello>output.txtifdiff-qexpected.txt output.txt>/dev/null;thenecho"测试通过"elseecho"测试失败"diff-uexpected.txt output.txtfi为后续学习自动化测试打下基础。
十、练习题(由浅入深)
基础篇
- 在当前目录创建
greet.c,输出 “Hello, Script!”。然后编写脚本run_greet.sh,一键编译并运行它。 - 解释脚本中
$?的含义,并修改脚本,使之在编译失败时用红色(可选)打印错误信息(提示:echo -e "\033[31m编译失败\033[0m")。 - 不使用
chmod +x,能否用其他方式运行脚本?(提示:bash 脚本名)
进阶篇
- 编写通用编译运行脚本
build.sh(如第八节),然后分别用它编译运行hello.c和greet.c。 - 在脚本中加入版本控制:如果可执行文件已经存在且比源文件新,则跳过编译直接运行(提示:用
test命令比较文件时间-nt)。 - 尝试在脚本中处理
gcc的警告信息,如果编译有警告但成功,打印 “编译成功,但有警告”。
提高篇
- 写一个脚本
batch_build.sh,它接收多个.c文件作为参数,依次编译运行每一个,遇到编译失败的跳过并报告。 - 结合
diff,给hello.c写一个自动化测试脚本:程序输出与预期输出比较,通过即打印 PASS,失败打印 FAIL 并显示差异。
十一、答案与解析
基础篇
- 创建
greet.c,内容类似。脚本只需把SOURCE改为greet.c,TARGET改为greet即可。 $?是上一条命令的退出码,0 表示成功,非 0 表示失败。添加颜色可写成echo -e "\033[31m编译失败\033[0m"。- 可以用
bash build_run.sh直接运行,此时不需要执行权限,因为 bash 读取文件内容执行。
进阶篇
- 通用脚本使用参数
./build.sh greet.c即可。 - 判断
if [ -f "$OUT" ] && [ "$OUT" -nt "$SRC" ]; then可跳过编译(-nt表示 “newer than”)。 - 可以用
gcc ... 2> warnings.txt将标准错误重定向,然后检查文件是否为空。
提高篇
batch_build.sh示例:#!/bin/bashforfilein"$@";doif[-f"$file"];thenout="${file%.c}"gcc"$file"-o"$out"if[$?-eq0];thenecho"运行$out..."./"$out"elseecho"编译$file失败"fielseecho"$file不存在"fidone- 参考第九节
test.sh。
十二、学习检查清单
- Shebang 的作用是什么?它必须放在第几行?
- 如何定义变量?引用变量时前面要加什么符号?
$?、$#、$1、$0分别代表什么?- 怎样给脚本添加执行权限?
- 脚本里如何用
if判断命令是否成功? ${变量%.c}的含义是什么?