sed与awk-第十章-底部抽屉
第十章 底部抽屉
getline 函数
getline:手动读取下一行输入。对比next:
next:读完下一行,直接跳回脚本开头,不再执行本行剩余代码。getline:读取一行,读取后停在当前位置继续往下执行后续代码,可以拿到读到的内容。
注意:
getline是语句,不要写括号getline()。
返回值:
| 返回值 | 含义 |
|---|---|
1 |
成功读到一行 |
0 |
到达文件末尾EOF |
-1 |
发生读取错误 |
用法1:读取主输入流(标准输入键盘),覆盖$0、更新NF/NR/FNR
语法:getline
读取下一行,把内容存入$0,自动拆分字段,更新NF、NR、FNR。
示例:匹配.SH "Name",读取紧跟的下一行,打印第一个字段
while循环读取连续多行:读取Related Commands标题之后所有行,拼接字符串
用法2:从指定文件读取输入
语法:getline < "文件名"
<是重定向符号,不是小于比较运算符。不会修改NR/FNR。
示例读取data文件所有行
读取标准输入,做交互提示
写了变量名
getline xxx < "file"→ 内容进变量 xxx,不碰 $0;
不写变量名getline < "file"→ 读到的行直接覆盖$0。
两种外部文件读取方式,都不会改动 NR FNR。
用法3:读到自定义变量(不改变$0,不拆分字段)
语法:getline 变量名
正确:
getline input
错误:input = getline,这是把函数返回值数字赋值给变量,不是读入的文本!
特点:
- 读到的内容存到变量,不会修改$0,不会拆分字段,NF不变
- NR、FNR仍然会+1
用法4:从管道读取shell命令输出
语法:"shell命令" | getline
执行shell命令,把命令输出交给getline。
- 直接覆盖
$0:
- 读到自定义变量,不修改
$0
循环读取命令输出全部行:
实战示例:替换文本中@date为系统当前日期
重要提醒:管道/文件用完,建议
close()关闭。
close() 函数
语法:close(字符串),字符串和当初打开的文件名/管道命令字符串完全一致。
作用:
- 释放已经用完的文件、管道;规避系统同时打开文件、管道数量上限。
- 同一个命令想要重复执行,必须先close关闭管道,否则命令不会重新运行。
示例关闭管道:
示例,输出到sort,读完后关闭管道,再读取临时文件:
system() 函数
语法:system("shell命令字符串")
- 执行shell命令;命令输出直接打印到屏幕,不会交给awk脚本处理。
- 返回值:命令退出状态,0代表执行成功,非0代表失败。
示例,创建目录,判断是否成功:
实现简化版soelim,解析troff .so包含文件:
区分记忆:
管道 | getline:拿到命令输出给awk内部变量$0。system():只执行命令,输出直接打印,awk拿不到输出内容。
基于菜单的命令生成器
综合案例:结合system()、getline。
-v CMDFILE=xxx在BEGIN块提前传入菜单配置文件名。getline < CMDFILE读取菜单配置文件。- 数组保存菜单描述和对应的shell命令。
system()执行用户选择的命令。
|
|
配置文件uucp_commands示例:
直接向文件和管道输出
print / printf 输出重定向到文件
print > "文件名":截断方式打开,覆盖原有内容。print >> "文件名":追加模式,保留原有内容,末尾新增。
注意:
>在这里是重定向,不是大于号。表达式需要比较大小要用括号包裹。
|
|
print输出到管道
语法:print | "shell命令"
把print输出,作为输入送给shell命令。
awk打开管道有数量上限;用完记得
close("wc -w")关闭管道。
大多数场景,优先使用shell管道awk '...' | wc -w,比awk内部管道可读性更好。
处理多个文件
系统对程序同时打开的文件、管道数量有限制。处理大量不同文件,用完必须close(),否则会耗尽资源脚本异常终止。
典型场景:按字段的值分割大文件,每行输出到不同文件。
man.split脚本示例,把一个大的man手册文件,拆分为多个独立小文件,使用数组缓存头部行,close(filename)及时关闭输出文件。
|
|
生成柱状报告(业务综合案例)
需求:RS="" 空行分隔记录,每条记录为客户订单;统计各个商品总销量、总金额,输出汇总报表。
输入数据记录之间空行隔开。
|
|
调试
awk没有内置调试器,依靠手工调试手段。两类bug:
- 语法错误:awk直接报错,容易定位。
- 逻辑错误:脚本运行完成无报错,输出结果不对,最难排查。
调试手段汇总
- 插入print打印中间变量,观察变量在关键点的值。
- 打印输入字段,确认输入数据和预想格式一致。
- 特殊标记打印,判断代码分支有没有走到:
- 注释掉部分代码,缩小出错范围(临时注释标记
#%方便批量恢复)
- 删减代码(slash‑and‑burn):备份脚本,一点点删除代码,定位哪段代码引发异常。
- 管道中间使用
tee保存中间输出,观察流水线每一步结果。 - 防御式编程:校验字段数量、数据合法性,捕获异常输入。
约束(awk内置限制)
POSIX传统awk有硬限制;gawk/mawk现代版本限制宽松很多。
表10‑1 awk传统版本约束
| 项目 | 约束上限 |
|---|---|
| 每个记录字段个数 | 100 |
| 单条输入记录字符数 | 3000 |
| 单条输出记录字符数 | 3000 |
| 单个字段字符数 | 1024 |
| printf格式字符串字符数 | 3000 |
| 字面字符串字符数 | 400 |
| 字符类[]内字符数量 | 400 |
| 同时打开文件数 | 15 |
| 同时打开管道数 | 1 |
Bell Labs awk支持
-mf N修改最大字段数;‑mr N修改记录最大长度,规避限制。
使用#!语法调用awk脚本
脚本文件第一行写解释器指令,脚本直接可执行,不需要每次敲awk -f。
正确写法:
必须带上
‑f,否则awk会把脚本文件名当成输入数据,报语法错误。
使用:
注意:
‑v参数不能写在#!行;var=value变量赋值,仍然可以在命令行传给脚本。
第十章核心速记清单
- getline系列
getline:读入下一行,覆盖$0,更新NF/NR/FNR。getline var:读到变量,不改$0,不拆分字段;禁止写var=getline。getline < "file":从文件读;<是重定向不是小于号。"cmd" | getline:执行shell命令读取输出到awk。- 返回值:1成功;0 EOF;‑1错误。
close("名字"):关闭已经用完的文件/管道;同一个管道想要再次运行命令,必须先close。system("cmd"):执行shell命令,输出直接打印;返回退出码;拿不到输出给awk内部。想要拿到输出,用管道+getline。- 输出重定向:
>覆盖,>>追加;print | "cmd"输出到管道;打开文件/管道记得close,防止到达系统上限。 - 调试:awk无调试器,依靠插入print打印中间变量、注释删减代码、tee保存中间结果。
- 传统awk存在记录长度、字段数、打开文件管道数量硬限制;gawk/mawk限制放宽。
#!/bin/awk -f脚本头,直接可执行awk脚本,‑f不可省略。
容易混淆对比表
写法 行为 next读取下一行,直接跳回脚本开头,后面代码不再执行 getline读取下一行,读完继续向下执行当前脚本剩余代码 system()执行命令,输出直接打印,awk拿不到输出 "cmd" | getline执行命令,把输出交给 awk 变量 $0 处理
文章作者 会写代码的小郎中
上次更新 2011-08-09
许可协议 CC BY-NC-ND 4.0