3、Linux命令行与Shell编程:常用命令、文件系统操作、权限管理、Shell脚本编写与调试

嵌入式Linux开发与传统的单片机裸机开发最大的区别在于,开发者需要在一个完整的操作系统之上工作。无论是编译内核、烧写根文件系统、调试驱动程序,还是配置交叉编译环境,都离不开Linux命令行。对于物联网设备而言,其资源受限、调试手段有限,熟练掌握命令行与Shell编程,不仅是提高效率的工具,更是定位问题、实现自动化测试与部署的核心能力。本章将围绕嵌入式开发中最常用的命令体系、文件系统层次结构、权限模型以及Shell脚本的编写与调试技巧展开,帮助开发者建立一套完整的命令行操作思维。

嵌入式Linux命令行 常用命令与文件操作 权限与用户管理 Shell脚本与调试 ls / cd / cp / mv / rm find / grep / tar / gzip /proc /sys /dev 虚拟文件系统 chmod / chown / chgrp su / sudo / useradd 权限位 rwx 与 umask 变量 / 条件 / 循环 函数 / 参数传递 sh -x / set -e 调试 目标:自动化构建 / 设备配置 / 日志分析

上图展示了本章的知识体系框架。在嵌入式开发中,这三个分支并非孤立存在,而是相互交织:文件操作是基础,权限管理保障系统安全,Shell脚本则将前两者串联起来实现自动化。下面我们逐一深入剖析。

3.1 常用命令与文件系统操作

3.1.1 核心原理:一切皆文件与虚拟文件系统

Linux设计哲学中“一切皆文件”的理念在嵌入式系统中体现得尤为明显。CPU寄存器、内存地址、GPIO引脚、传感器数据,在Linux下都被抽象为文件。开发者通过操作文件描述符即可完成硬件访问。嵌入式Linux根文件系统通常遵循FHS(文件系统层次标准),但针对嵌入式环境做了精简。以下表格展示了嵌入式系统中常见的目录及其用途:

目录用途嵌入式注意事项
/bin用户可执行命令(ls, cp)通常为busybox提供的精简版本
/sbin系统管理命令(ifconfig, fdisk)仅root可执行部分命令
/dev设备节点文件使用devtmpfs或mdev动态管理
/proc内核运行时信息(内存、CPU)procfs,反映内核状态
/sys设备驱动模型与属性sysfs,用于控制设备参数
/etc配置文件只读挂载时需注意临时修改
/tmp临时文件通常为tmpfs,断电丢失

在嵌入式调试中,/proc和/sys是两个高频操作目录。例如,查看系统内存信息使用cat /proc/meminfo,调整GPIO输出电平则通过/sys/class/gpio下的文件完成。

3.1.2 操作流程:文件查找、打包与传输

嵌入式开发中经常需要从主机向目标板传输文件、查找特定配置项、打包日志。以下是一套标准操作流程:

  1. 文件定位:使用find /etc -name "*.conf"查找配置文件;使用grep -r "CONFIG_XXX" /在内核源码或根文件系统中递归搜索字符串。
  2. 打包压缩:使用tar czvf log.tar.gz /var/log将日志打包,便于从设备导出。
  3. 网络传输:在开发板上使用scp user@host:/path/file .拉取文件,或使用tftp、nfs挂载根文件系统进行实时调试。
  4. 磁盘操作:使用df -h查看Flash分区使用率,mount -t jffs2 /dev/mtdblock3 /mnt挂载文件系统。
经验提醒:在资源受限的嵌入式设备上,避免使用grep -r直接搜索整个根目录,这会导致内存耗尽。建议先使用find限定目录范围,再配合xargs进行过滤。

3.1.3 案例拆解:交叉编译环境的文件操作

背景:某物联网网关设备使用ARM Cortex-A7处理器,开发者在x86主机上交叉编译了一个应用程序,需要部署到目标板并验证运行。

初始现象:程序在目标板上提示No such file or directory,但文件明明存在。

分析过程:首先使用file app命令检查二进制格式,发现是x86架构而非ARM架构。进一步使用readelf -h app查看ELF头,确认架构不匹配。此外,检查动态链接器路径,发现程序依赖的/lib/ld-linux-armhf.so.3在目标板上不存在。

解决步骤:

  1. 重新使用交叉编译器编译:arm-linux-gnueabihf-gcc app.c -o app。
  2. 使用arm-linux-gnueabihf-strip app去除符号表,减小体积。
  3. 通过NFS挂载主机目录,直接运行测试:mount -t nfs 192.168.1.10:/work /mnt -o nolock。
  4. 确认运行后,再使用cp命令固化到/usr/bin。

结果对比:修正后程序正常运行,CPU占用率从异常时的100%降至正常水平的5%。复盘:文件操作不仅仅是命令的记忆,更需要结合file、readelf等工具进行二进制兼容性判断。

3.1.4 常见误区与总结

  • 误区一:在嵌入式设备上使用rm -rf /进行清理,极易导致系统崩溃。应使用rm -rf /path/to/specific/dir并确认路径。
  • 误区二:混淆/dev下的设备节点与/sys中的设备属性。设备节点用于打开和读写,而/sys用于配置参数和查看状态。
  • 误区三:忽略文件系统类型。嵌入式常用jffs2、ubifs、ext4,不同文件系统对掉电保护、磨损均衡的支持不同,操作前需确认挂载参数。

总结而言,文件操作的核心在于理解路径、类型与挂载关系。熟练使用ls -l查看文件属性,du -sh查看目录大小,是排查存储问题的第一步。

3.2 权限管理

3.2.1 核心原理:UGO模型与进程权限

Linux权限模型基于用户(User)、组(Group)、其他(Other)三类主体,每类主体对文件具有读(r=4)、写(w=2)、执行(x=1)三种权限。嵌入式系统中,权限管理直接关系到系统安全。例如,一个Web服务器进程如果以root权限运行,一旦被攻击,攻击者将获得整个设备的控制权。因此,嵌入式Linux通常采用最小权限原则,将不同服务分配给不同用户运行。

权限的表示方法有两种:符号模式(如rwxr-xr--)和数字模式(如754)。数字模式的计算方式为:所有者权限数字+组权限数字+其他权限数字。例如754表示所有者可读写执行(7=4+2+1),组可读执行(5=4+1),其他只读(4)。

3.2.2 操作流程:修改权限与属主

在嵌入式开发中,经常需要修改设备节点权限或应用程序的执行权限。标准流程如下:

  1. 查看当前权限:ls -l /dev/ttyS0,输出如crw-rw---- 1 root dialout 4, 64 Jan 1 00:00 /dev/ttyS0。
  2. 修改权限:chmod 666 /dev/ttyS0,使所有用户可读写。
  3. 修改属主:chown root:root /dev/ttyS0,将属主和组都改为root。
  4. 设置粘滞位:对于共享目录如/tmp,使用chmod +t /tmp防止用户删除他人文件。
  5. 使用umask:在/etc/profile中设置umask 022,使新建文件默认权限为644,新建目录为755。
风险提示:在嵌入式设备上,切勿随意对/dev下的设备节点执行chmod 777。这会使任何用户都能直接操作硬件,例如格式化Flash或控制电机,造成不可逆的物理损坏。

3.2.3 案例拆解:守护进程的权限降级

背景:某智能摄像头设备中的视频采集服务需要访问/dev/video0和GPIO引脚,但该服务存在远程代码执行漏洞。

初始目标:即使服务被攻破,攻击者也无法获得root权限。

实施步骤:

  1. 创建专用用户:useradd -r -s /bin/false camera,创建系统用户且禁止登录。
  2. 修改设备节点权限:chown camera:video /dev/video0,chmod 660 /dev/video0。
  3. 在服务启动脚本中,先以root执行初始化(如加载内核模块),然后使用setuid切换到camera用户运行主程序。
  4. 使用capsh --drop=all -- -c "exec ./camera_service"丢弃所有能力(capability),仅保留必要的CAP_SYS_ADMIN。

结果:在一次渗透测试中,攻击者成功执行了system("reboot"),但由于权限不足,命令执行失败,设备未受影响。复盘:权限管理需要结合用户、组、能力(capabilities)和SELinux/AppArmor等多层机制,单靠chmod是不够的。

3.2.4 常见误区与总结

  • 误区一:认为嵌入式设备不需要密码策略。实际上,物联网设备常因默认密码(如root/root)被入侵。
  • 误区二:使用chmod -R 777 /解决权限问题,这会彻底破坏系统安全模型。
  • 误区三:忽略文件系统挂载选项。在/etc/fstab中,对只读分区添加ro选项,对无需执行的分区添加noexec,能有效提升安全性。

权限管理的核心是“最小权限”与“可审计性”。在嵌入式Linux中,应尽量使用capabilities替代完全的root权限,例如使用setcap cap_net_raw+ep ./ping赋予程序原始套接字能力,而不是直接setuid root。

3.3 Shell脚本编写与调试

3.3.1 核心原理:Shell解释器与脚本执行机制

嵌入式Linux中默认的Shell通常是BusyBox提供的ash( Almquist Shell),它比bash更轻量,但语法基本兼容。Shell脚本本质上是命令的集合,解释器逐行读取并执行。脚本第一行#!/bin/sh指定了解释器路径。在嵌入式环境中,脚本常用于系统初始化(/etc/init.d/rcS)、启动应用程序、监控硬件状态等。

Shell脚本的变量、条件判断、循环与函数是核心语法。变量赋值VAR=value(注意等号两侧不能有空格),引用变量使用$VAR或${VAR}。条件判断使用if [ condition ]; then ... fi,注意[两侧必须有空格。循环包括for、while和until。

3.3.2 调试方法:从echo到sh -x

嵌入式脚本调试受限于资源,无法使用复杂的IDE。常用调试手段如下:

调试手段命令/语法适用场景
打印变量echo "DEBUG: VAR=$VAR"快速定位变量值
语法检查sh -n script.sh检查语法错误,不执行
跟踪执行sh -x script.sh显示每条命令的执行过程,前缀+
设置退出set -e任何命令返回非零状态立即退出
设置未定义变量报错set -u防止使用未初始化的变量

在嵌入式设备上,由于sh -x输出较多,建议将输出重定向到文件:sh -x /etc/init.d/rcS > /tmp/debug.log 2>&1,然后通过串口或网络查看。

3.3.3 案例拆解:设备启动自检脚本

背景:某工业数据采集器,需要在开机时检查传感器通信、网络连接和存储空间,若异常则通过LED闪烁报警。

脚本设计:

#!/bin/sh
# /usr/bin/self_check.sh
LED_PIN=18
# 初始化GPIO
echo $LED_PIN > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio$LED_PIN/direction

# 定义LED控制函数
led_blink() {
    for i in 1 2 3; do
        echo 1 > /sys/class/gpio/gpio$LED_PIN/value
        sleep 0.2
        echo 0 > /sys/class/gpio/gpio$LED_PIN/value
        sleep 0.2
    done
}

# 检查传感器I2C通信
if ! i2cget -y 2 0x48 0x00 > /dev/null 2>&1; then
    echo "Sensor I2C error"
    led_blink
    exit 1
fi

# 检查网络连通性
if ! ping -c 1 -W 2 192.168.1.1 > /dev/null 2>&1; then
    echo "Network unreachable"
    led_blink
    exit 2
fi

# 检查存储空间
AVAIL=$(df /data | awk 'NR==2 {print $4}')
if [ "$AVAIL" -lt 1024 ]; then
    echo "Insufficient storage: ${AVAIL}KB"
    led_blink
    exit 3
fi

echo "All checks passed"
exit 0

调试过程:在目标板上执行sh -x /usr/bin/self_check.sh,发现i2cget命令不存在。原来BusyBox编译时未包含i2c工具。解决方案:改用dd命令直接读取/dev/i2c-2设备节点,或者重新编译BusyBox添加i2cget。结果:修改后脚本正常运行,开机自检时间从原来的5秒缩短至2秒。复盘:脚本调试不仅要关注逻辑,还要关注目标板上的命令集是否完整。

3.3.4 常见误区与总结

  • 误区一:在脚本中使用bash特有语法(如[[ ]]、数组),但嵌入式默认sh是ash,导致语法错误。应使用POSIX兼容语法。
  • 误区二:忽略命令的退出状态码。在set -e模式下,grep未匹配到内容会返回1,导致脚本意外退出。应使用if grep -q ...; then结构。
  • 误区三:在脚本中直接操作硬件寄存器而不通过设备节点,这违背了Linux的设备模型,且不可移植。

Shell脚本调试的核心是“增量验证”。在嵌入式环境中,建议先编写最小可执行片段,验证通过后再组合成完整脚本。同时,利用trap命令捕获信号进行清理操作,例如trap 'echo "Script interrupted"; cleanup' INT TERM。

本章核心要点回顾:
  • 文件操作需结合/proc、/sys虚拟文件系统理解硬件抽象。
  • 权限管理应遵循最小权限原则,善用用户、组和capabilities。
  • Shell脚本调试优先使用sh -x和set -e,注意BusyBox的语法兼容性。
  • 所有命令操作前,先确认目标板上的命令集和文件系统类型,避免“主机思维”误导。

通过本章的学习,读者应能够在嵌入式Linux环境下熟练进行文件管理、权限配置和脚本自动化。这些技能是后续进行驱动开发、系统移植和物联网应用调试的基石。在实际项目中,建议开发者建立自己的常用命令速查表,并针对目标硬件定制一套自检脚本,这将极大提升开发与排障效率。