4、Shell脚本基础:变量、条件判断、循环、函数、实战:一键环境检测脚本

Shell脚本,说白了就是你把一堆Linux命令写进一个文件里,让机器自动帮你执行。我刚开始做嵌入式那会儿,总觉得敲命令才够酷,后来项目一多,每次都要手动敲十几条检查命令,烦得不行。嗯,从那以后,我写任何项目的第一件事,就是先写个环境检测脚本。

这一章,咱们就把Shell脚本的核心知识点过一遍。变量怎么用、条件怎么写、循环怎么跑、函数怎么封装。最后,我会带你手写一个实战脚本——一键检测你的交叉编译链、系统库、工具链版本是否齐全。

核心目标:学完这一章,你能自己写出一个可复用的环境检测脚本,再也不用手动敲命令去确认依赖了。

4.1 变量:存东西的盒子

Shell里的变量,不需要声明类型。你直接赋值就行。我个人习惯用大写字母命名变量名,这样一眼就能看出这是个环境变量或者全局变量。

#!/bin/bash
# 定义一个变量
TOOLCHAIN_PATH="/usr/local/arm-gcc"
# 使用变量,前面加$
echo "我的交叉编译链路径是:$TOOLCHAIN_PATH"

这里有个坑,我刚开始学的时候踩过——赋值号 = 两边不能有空格。你写 TOOLCHAIN_PATH = "/usr/local/arm-gcc" 就错了,Shell会以为你在执行一个叫 TOOLCHAIN_PATH 的命令。

注意:变量赋值时,等号两边绝对不能有空格。这是新手最容易犯的错误。

还有一种特殊变量,叫环境变量。比如 $PATH、$HOME。我在项目中经常用 $? 来获取上一条命令的执行结果——0表示成功,非0表示失败。这个在后面的条件判断里非常有用。

4.2 条件判断:让脚本有脑子

条件判断,说白了就是「如果……就……」。Shell里用 if、elif、else、fi 来写。注意结尾的 fi 是 if 倒过来写,这是Shell的特色。

#!/bin/bash
# 检查一个文件是否存在
if [ -f "/usr/local/arm-gcc/gcc" ]; then
    echo "交叉编译链已安装"
else
    echo "交叉编译链未安装,请先安装"
fi

你想想看,为什么用 [ -f ] 这种写法?其实 [ 是一个命令,等价于 test 命令。所以 [ -f 文件 ] 就是在问「这个文件存在吗?」。

常用的判断条件我列个表给你:

表达式 含义
[ -f 文件 ] 文件是否存在且为普通文件
[ -d 目录 ] 目录是否存在
[ -z 字符串 ] 字符串是否为空
[ 变量1 = 变量2 ] 两个字符串是否相等
[ 数字1 -eq 数字2 ] 两个数字是否相等
小技巧:判断数字时用 -eq、-ne、-gt、-lt,不要用 = 或 !=。我曾经在比较版本号时用错了,结果脚本一直报错,排查了半天才发现是这个问题。

4.3 循环:重复的事情交给机器

循环有两种常用形式:for 循环和 while 循环。我个人更常用 for 循环,因为它写起来更直观。

#!/bin/bash
# 遍历一个列表
for tool in gcc g++ gdb make cmake; do
    echo "检查工具:$tool"
    which $tool
done

这段代码会依次检查 gcc、g++、gdb、make、cmake 这几个工具是否在系统路径中。我在写环境检测脚本时,经常用这种模式——把要检查的工具名放在一个列表里,然后循环去检测。

while 循环一般用于不确定循环次数的情况。比如读取一个文件的内容:

#!/bin/bash
# 逐行读取一个文件
while read line; do
    echo "当前行内容:$line"
done < "依赖列表.txt"

嗯,这里要注意,while read line 这种写法会把文件中的每一行赋值给变量 line,然后执行循环体。我在项目中用它来批量处理日志文件,非常好用。

4.4 函数:把重复代码打包

函数就是把一段代码封装起来,给它起个名字。以后想用这段代码,直接喊名字就行。Shell函数的定义很简单:

#!/bin/bash
# 定义一个函数
check_tool() {
    local tool_name=$1
    if which $tool_name &>/dev/null; then
        echo "$tool_name 已安装"
        return 0
    else
        echo "$tool_name 未安装"
        return 1
    fi
}

# 调用函数
check_tool "gcc"
check_tool "arm-linux-gnueabihf-gcc"

这里我用了 local 关键字来声明局部变量。为什么?因为如果不加 local,函数里的变量就是全局的,可能会污染外面的变量。我曾经在写一个大型脚本时,因为函数里的变量没加 local,导致两个函数互相覆盖了对方的变量值,查了整整一个下午。

最佳实践:函数内部的所有变量,只要不是特意要暴露给外部的,一律加 local 声明。

函数的参数通过 $1、$2、$3 来获取。返回值用 return 关键字,但注意返回值只能是 0-255 之间的整数。如果你需要返回字符串,可以用 echo 输出,然后在调用时用 $() 捕获。

4.5 实战:一键环境检测脚本

好了,知识点都讲完了。现在咱们来写一个真正能用的脚本。这个脚本会检测嵌入式开发中最常用的几个工具和库:

  • 交叉编译链(arm-linux-gnueabihf-gcc)
  • 构建工具(make、cmake)
  • 调试工具(gdb、gdbserver)
  • 系统库(ncurses、zlib)

先画个流程图,让你对整个脚本的逻辑有个整体认识:

一键环境检测脚本流程图 开始检测 检测交叉编译链 (arm-linux-gnueabihf-gcc) 检测构建工具 (make、cmake) 检测调试工具 (gdb、gdbserver) 输出检测报告

好,现在来看完整的脚本代码:

#!/bin/bash

# ============================================
# 一键环境检测脚本
# 作者:资深嵌入式工程师
# 用途:检测嵌入式开发依赖环境
# ============================================

# 定义颜色变量
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # 无颜色

# 检测结果计数器
PASS_COUNT=0
FAIL_COUNT=0

# 函数:检测单个工具
check_tool() {
    local tool_name=$1
    local tool_path=$(which $tool_name 2>/dev/null)
    
    if [ -n "$tool_path" ]; then
        echo -e "${GREEN}[PASS]${NC} $tool_name 已安装 -> $tool_path"
        PASS_COUNT=$((PASS_COUNT + 1))
        return 0
    else
        echo -e "${RED}[FAIL]${NC} $tool_name 未安装"
        FAIL_COUNT=$((FAIL_COUNT + 1))
        return 1
    fi
}

# 函数:检测系统库
check_library() {
    local lib_name=$1
    # 使用 ldconfig 或 dpkg 检测库文件
    if ldconfig -p 2>/dev/null | grep -q "$lib_name"; then
        echo -e "${GREEN}[PASS]${NC} 系统库 $lib_name 已安装"
        PASS_COUNT=$((PASS_COUNT + 1))
        return 0
    else
        echo -e "${RED}[FAIL]${NC} 系统库 $lib_name 未安装"
        FAIL_COUNT=$((FAIL_COUNT + 1))
        return 1
    fi
}

# 主检测流程
echo "=========================================="
echo "  嵌入式开发环境检测脚本"
echo "  检测时间:$(date)"
echo "=========================================="
echo ""

# 1. 检测交叉编译链
echo "【1/4】检测交叉编译链..."
check_tool "arm-linux-gnueabihf-gcc"
check_tool "arm-linux-gnueabihf-g++"
echo ""

# 2. 检测构建工具
echo "【2/4】检测构建工具..."
check_tool "make"
check_tool "cmake"
echo ""

# 3. 检测调试工具
echo "【3/4】检测调试工具..."
check_tool "gdb"
check_tool "gdbserver"
echo ""

# 4. 检测系统库
echo "【4/4】检测系统库..."
check_library "libncurses"
check_library "libz"
echo ""

# 输出汇总报告
echo "=========================================="
echo "  检测报告"
echo "  通过:$PASS_COUNT  失败:$FAIL_COUNT"
echo "=========================================="

if [ $FAIL_COUNT -eq 0 ]; then
    echo -e "${GREEN}恭喜!所有依赖环境已就绪。${NC}"
    exit 0
else
    echo -e "${YELLOW}警告:有 $FAIL_COUNT 项依赖未安装,请先安装后再继续。${NC}"
    exit 1
fi
使用说明:将上面的代码保存为 check_env.sh,然后执行 chmod +x check_env.sh 赋予执行权限,最后运行 ./check_env.sh 即可。

这个脚本里,我用了几个小技巧:

  • 颜色输出:用 \033[0;32m 这样的转义序列,让通过和失败的结果一目了然。
  • 计数器:用 PASS_COUNT 和 FAIL_COUNT 统计结果,最后输出汇总报告。
  • 函数封装:把检测逻辑封装成函数,以后想增加检测项,只需要调用函数就行,不用重复写代码。

你想想看,如果每次搭建新环境都要手动敲命令去确认,那得多浪费时间?有了这个脚本,一键运行,所有依赖状态清清楚楚。我在项目中每次换新电脑或者搭建新服务器,第一件事就是跑这个脚本。

注意:脚本中的 check_library 函数使用了 ldconfig -p 来检测库文件。如果你的系统没有 ldconfig 命令(比如某些嵌入式板卡),可以改用 dpkg -l 或 rpm -qa 来检测。

好了,这一章的内容就到这里。变量、条件判断、循环、函数,再加上一个完整的实战脚本,你已经掌握了Shell脚本的核心技能。以后写自动化脚本,心里就有底了。

可可精品资料库