第1章 Shell编程规范与变量 —— 学习笔记

实验环境:OpenEuler 24.03,默认 Shell 为 /bin/bash/bin/sh/bin/bash 的软链接,保证向下兼容)

0. 本章框架(一图看懂)

Shell 脚本基础 ──► 编程规范 + 3种执行方式
I/O 控制       ──► 重定向(> >> < 2> &>) + 管道(|)
变量体系       ──► 自定义 / 环境 / 只读 / 位置 / 预定义
文本三剑客(入门) ► sort(排序) + uniq(去重计数) + tr(字符处理)

技能目标:①掌握脚本核心规范与执行逻辑;②熟练使用各类变量与数组;③精通重定向、管道原理与应用;④掌握 sort/uniq/tr 高级用法与组合场景。


1. Shell 脚本基础

1.1 脚本编程规范(四条核心规范)

规范要求说明示例
脚本声明(首行)#!/bin/bash 指定解释器,必选#!/bin/bash
注释说明# 开头,标注功能、作者、日期、参数# 功能:批量检测服务器端口状态
命令规范性命令小写;路径用绝对路径(避免环境变量问题)cd /boot && ls -lh
可执行权限chmod +x 或直接用解释器执行chmod +x script.sh

标准脚本示例(first.sh):

#!/bin/bash
# 脚本名称: first.sh
# 功能: 查看 /boot 目录下的内核文件信息
# 系统环境: OpenEuler 24.03
cd /boot
echo "【OpenEuler 24.03】当前目录为:"
pwd
echo "【OpenEuler 24.03】/boot 目录下以 vml 开头的内核文件:"
ls -lh vml*

1.2 三种执行方式(必考对比)

执行方式命令适用场景权限要求
绝对/相对路径执行./first.sh日常手动执行、脚本调度需 x 权限
解释器直接执行bash first.sh / sh first.sh无 x 权限、跨 Shell 兼容无需 x 权限
source/点号加载source first.sh / . first.sh让脚本修改当前 Shell 环境(如定义全局变量)无需 x 权限

🛡️ 企业提示:禁止直接修改系统自带 Shell 脚本(如 /etc/rc.d/ 下),改前必须备份。


2. 重定向与管道

2.1 标准 I/O 与文件描述符(底层基础)

标准 I/O文件描述符默认设备作用
标准输入 STDIN0键盘命令读取输入
标准输出 STDOUT1显示器正常结果输出
标准错误 STDERR2显示器错误信息输出

本质:重定向与管道就是修改文件描述符的关联设备,实现数据定向传输。

2.2 重定向

符号含义说明
> (= 1>覆盖输出重定向文件不存在则新建,存在则覆盖
>>(= 1>>追加输出重定向保留原内容
< (= 0<输入重定向命令从文件读输入,实现非交互式执行
2> / 2>>错误(覆盖/追加)重定向结果与错误分离
&>混合重定向1 和 2 同时定向,等价 > file 2>&1&> /dev/null = 丢弃全部输出

典型企业场景:

uname -p > kernel_arch.txt          # 记录结果
uname -r >> kernel_arch.txt         # 追加结果
echo "OpEuler@2024" > pass.txt
useradd jerry
passwd --stdin jerry < pass.txt     # 输入重定向:非交互批量设密码
tar zcf /nonedir/etc.tgz /etc/ 2> backup_error.log   # 采集错误日志
./configure --prefix=/usr/local/nginx &> /dev/null   # 忽略无关输出

注:OpenEuler 中 passwd --stdin 若失败,可能与 SELinux 策略有关,可临时 setenforce 0

2.3 管道 |

  • 作用:将左侧命令的标准输出作为右侧命令的标准输入,链式协同处理。
  • 关键点:只传递 STDOUT,STDERR 不经过管道(需先重定向)。
  • 原理:数据暂存管道缓冲区,流式传输,不生成临时文件,效率高于重定向中转。
  • 支持多级串联:cmd1 | cmd2 | cmd3

高频组合示例:

# 提取用户名+登录Shell
grep "/bin/bash$" /etc/passwd | awk -F: '{print $1,$7}'  
# 根分区使用率,如 11%
df -hT | grep "/$" | awk '{print $6}'                   
# 统计进程数(-v grep 排除grep自身)
ps -ef | grep nginx | grep -v grep | wc -l                

3. Shell 变量

变量按类型分为:自定义变量、环境变量、只读变量、位置变量、预定义变量

3.1 自定义变量(局部变量)

定义三条强制规则

  1. 变量名以字母/下划线开头,后跟字母/数字/下划线;禁止特殊字符与空格;
  2. = 两边不能有空格(空格是命令分隔符);
  3. 值含空格/特殊字符时用双/单引号括起。
#正确
Product=OpenEuler        
Env="Production Server"  
#错误
Product = OpenEuler      
2Version=24.03           

引用与边界界定

echo $Product            # OpenEuler
echo $Product24.03       # 输出 .03 —— 被识别为未定义变量 $Product24
echo ${Product}24.03     # OpenEuler24.03 —— 用 {} 明确变量边界

赋值特殊操作

方式特点示例
双引号 ""弱引用解析 $ 变量引用和 \ 转义Env="${Product} Production Env"
单引号 '' 强引用一切字符原样,禁止变量解析Env='${Product} $Version'
命令替换命令的 STDOUT 作为变量值p=$(which useradd)推荐,支持嵌套)/ p=`which useradd`
read 交互赋值从键盘读入read -p "请输入备份目录:" Backup_Dir
# 嵌套命令替换(只有 $() 支持):查 ls 命令所属包的配置文件
Ls_Conf=$(rpm -qc $(rpm -qf $(which ls)))

作用域:局部 → 全局(export)

Product=OpenEuler          # 仅当前 Shell 有效,进子 Shell(bash) 后为空
export Product Version     # 导出为全局变量,子 Shell 可用
export FQDN=www.xxx.com    # 一步创建并导出

export 只在当前会话有效;要永久生效须写入 /etc/profile(全局)或 ~/.bash_profile(当前用户)。

3.2 数值运算(expr)

Bash 默认只支持整数运算(小数要用 bc/awk)。

X=35; Y=16
expr $X + $Y        # 51   【注意运算符两侧必须有空格】
expr $X \* $Y       # 560  【乘法必须转义 \*】
expr $X / $Y        # 2    除法取整
expr $X % $Y        # 3    取模
Ycube=$(expr $Y \* $Y \* $Y)   # 结果赋值需命令替换 → 4096

3.3 环境变量(系统预定义的全局变量,名通常大写)

查看:env(仅环境变量)/ set(全部变量)。核心必记:

变量作用
USER当前登录用户名
HOME当前用户宿主目录
PWD当前工作目录
PATH命令搜索路径(冒号分隔,按序查找,找不到报"未找到命令")
LANG语言与字符集,如 zh_CN.UTF-8
SHELL登录 Shell
HISTSIZE历史命令条数(默认 1000)

PATH 实战——让脚本任意目录可执行(两种方法,推荐第2种):

# 方法1:把脚本放进 PATH 已含目录(如 /usr/local/bin)
# 方法2:把脚本目录追加进 PATH(临时生效)
PATH="$PATH:/root/scripts"

永久配置环境变量

配置文件作用域场景
/etc/profile所有用户(全局)系统级 PATH、LANG
~/.bash_profile仅当前用户个人脚本目录、HISTSIZE
vim ~/.bash_profile     # 末尾加 export HISTSIZE=200
source ~/.bash_profile   # 立即生效,无需重启终端

3.4 只读变量

Log_Path=/var/log/xx.log
readonly Log_Path        # 设定后不可改、不可删(unset 也删不掉),关闭会话即失效
readonly -p              # 查看只读变量

用途:固定脚本核心参数(路径、安装目录),防误改。

3.5 位置变量(脚本命令行传参)

  • $1~$9:脚本名后第 1~9 个参数;超过 9 个用 ${10}${11}$0 是脚本名(属预定义变量)。
#!/bin/bash   # adder2num.sh
SUM=$(expr $1 + $2)
echo "【OpenEuler 加法运算】$1 + $2 = $SUM"
# ./adder2num.sh 12 34  →  【OpenEuler 加法运算】12 + 34 = 46

3.6 预定义变量(只读、专为脚本设计,必背)

变量作用应用场景
$#位置参数个数参数个数校验
$*所有位置参数(作为一个整体)批量处理所有参数
$?前一条命令的返回状态,0=成功,非0=失败脚本容错的核心(几乎每个企业脚本都用)
$0当前脚本名称日志输出、帮助提示

综合实战(备份脚本 mybak.sh,串起 $*$#$0、时间戳、混合重定向):

#!/bin/bash
TARFILE=openeuler-bak-$(date +%s).tgz     # UNIX 时间戳保证压缩包名唯一
tar zcf $TARFILE $* &> /dev/null          # 批量备份所有传入参数
echo "已执行脚本:$0"
echo "共备份对象个数:$# 个"
echo "备份对象列表:$*"
echo "备份压缩包:$TARFILE"

4. 文本处理工具 sort / uniq / tr

4.1 sort —— 按行排序

语法:sort [选项] 文件命令 | sort [选项]默认按字符字典序(数字大小排序必须加 -n,否则 10 排在 2 前)。

选项作用
-f忽略大小写
-b忽略行首空格
-n数值排序
-r反向(降序)
-u排序并去重
-t指定分隔符(默认 Tab)
-k指定排序列(配合 -t
-o 文件结果输出到文件(替代 >
-M按月份排序(Jan–Dec)
sort /etc/passwd                       # 基础字符排序
sort -t: -nrk 3 /etc/passwd            # 按 UID(第3列·冒号分隔)降序
sort -t: -nk 3 /etc/passwd -o user_uid.txt
sort -nu num.txt                       # 数值升序 + 去重

4.2 uniq —— 去重(必须搭配 sort)

uniq 只对"连续"重复行生效 → 标准姿势:sort 文件 | uniq [选项]

选项作用
-c去重并计数(行首显示次数)
-d仅显示重复的行
-u仅显示只出现一次的行
sort testfile | uniq        # 去重
sort testfile | uniq -c     #   3 CentOS 7 / 3 OpenEuler 24.03 / 2 Ubuntu 22.04
sort testfile | uniq -d     # 找重复数据
sort testfile | uniq -u     # 提取唯一数据(如单独出现1次的 SUSE 15)

4.3 tr —— 字符流处理(只读标准输入/管道,不能直接处理文件)

语法:命令 | tr [选项] 字符集1 [字符集2];字符集可用单字符、范围(a-z0-9)、特殊字符(\n\t)。
替换规则:两字符集长度建议一致;字符集1更长时,超出部分用字符集2的最后一个字符替换。

选项作用
-d删除字符集1中所有字符
-s压缩连续重复字符,只留一个
-c取补集(匹配不属于字符集1的字符)
-t截断字符集1至与字符集2等长(默认行为)
echo "OPENEULER 24.03" | tr 'A-Z' 'a-z'          # 大写→小写
echo "thissss is ..." | tr -s 'sn'                 # 压缩连续 s/n
echo "hello    world" | tr -s ' '                 # 压缩连续空格
echo 'hello world' | tr -d 'od'                   # 删除 o、d → hell wrl
echo "OpenEuler 24.03" | tr -d '0-9'              # 删除所有数字
echo "..." | tr -c -d 'a-zA-Z '                   # -c -d 组合:只保留字母和空格
cat testfile | tr '\n' ' '                        # 多行→一行
cat data.tsv | tr '\t' ',' > data.csv             # TSV→CSV

5. 高频考点 & 易错清单

考点自查(对应文档"本章核心考点")

  1. 脚本必备结构:#!/bin/bash + 注释 + 执行权限;3 种执行方式的区别(source 能改当前环境、bash 不需 x 权限)。
  2. 重定向五件套:>>><2>&>;文件描述符 0/1/2;管道只传 STDOUT。
  3. 变量赋值规则(= 无空格)、${} 边界、双/单引号区别、export、expr(空格、\*、仅整数)。
  4. 位置变量 $1~$9(超9个用 ${10})与预定义变量 $#$*$?$0 的用途。
  5. sort -n/-r/-t/-k/-u/-ouniq -c/-d/-u(先 sort 后 uniq)、tr -s/-d/-c 组合。

易错点速记

  • = 两边有空格 → 报错;变量名数字开头 → 报错。
  • $Product24 会被当成一个整体变量名 → 用 ${Product}24
  • 单引号完全不解析,双引号解析 $\,反撇号/$() 解析命令 → 优先级记忆。
  • expr:运算符两侧必须空格,乘号写 \*,不支持小数。
  • 数字排序忘加 -n → “10 < 2” 的字典序陷阱。
  • 直接 uniq file 效果不对 → 必须 sort file | uniq
  • tr 不接受文件名参数 → cat file | tr ...
  • $? 紧跟被判断命令使用,中间插别的命令就被覆盖。
  • export 不写进配置文件,重启终端就丢。

6. 一页速查表

需求命令/写法
查看系统支持的 Shellcat /etc/shells
脚本执行(无权限)bash xx.sh
脚本改变当前 Shell 环境source xx.sh. xx.sh
丢弃所有输出cmd &> /dev/null
非交互设密码passwd --stdin user < pass.txt
弱/强引用"含$变量" / '原样字符'
取命令结果v=$(cmd)(可嵌套)
全局变量export 名(本会话)/ 写入 profile(永久)
整数运算expr $a \* $b(空格!)
参数个数/全部/状态/脚本名$# / $* / $? / $0
按 UID 排序sort -t: -nk 3 /etc/passwd
去重计数`sort f
大小写转换tr 'A-Z' 'a-z'
提取纯数字tr -c -d '0-9\n'
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐