如何把 awk 脚本移植到 Python


如何把 awk 脚本移植到 Python

文章插图
 
将一个 awk 脚本移植到 Python 主要在于代码风格而不是转译 。-- Moshe Zadka(作者)
 
脚本是解决问题的有效方法 , 而 awk 是编写脚本的出色语言 。它特别擅长于简单的文本处理 , 它可以带你完成配置文件的某些复杂重写或目录中文件名的重新格式化 。
何时从 awk 转向 Python但是在某些方面 , awk 的限制开始显现出来 。它没有将文件分解为模块的真正概念 , 它缺乏质量错误报告 , 并且缺少了现在被认为是编程语言工作原理的其他内容 。当编程语言的这些丰富功能有助于维护关键脚本时 , 移植将是一个不错的选择 。
我最喜欢的完美移植 awk 的现代编程语言是 Python 。
在将 awk 脚本移植到 Python 之前 , 通常值得考虑一下其原始使用场景 。例如 , 由于 awk 的局限性 , 通常从 Bash 脚本调用 awk 代码 , 其中包括一些对 sed、sort 之类的其它命令行常见工具的调用 。最好将所有内容转换为一个一致的 Python 程序 。有时 , 脚本会做出过于宽泛的假设 , 例如 , 即使实际上只运行一个文件 , 该代码也可能允许任意数量的文件 。
在仔细考虑了上下文并确定了要用 Python 替代的东西之后 , 该编写代码了 。
标准 awk 到 Python 功能以下 Python 功能是有用的 , 需要记住:
with open(some_file_name) as fpin: for line in fpin: pass # do something with line此代码将逐行循环遍历文件并处理这些行 。
如果要访问行号(相当于 awk 的 NR) , 则可以使用以下代码:
with open(some_file_name) as fpin: for nr, line in enumerate(fpin): pass # do something with line在 Python 中实现多文件的 awk 式行为如果你需要能够遍历任意数量的文件同时保持行数的持续计数(类似 awk 的 FNR) , 则此循环可以做到这一点:
def awk_like_lines(list_of_file_names): def _all_lines(): for filename in list_of_file_names: with open(filename) as fpin: yield from fpin yield from enumerate(_all_lines())此语法使用 Python 的生成器和 yield from 来构建迭代器 , 该迭代器将遍历所有行并保持一个持久计数 。
如果你需要同时使用 FNR 和 NR , 这是一个更复杂的循环:
def awk_like_lines(list_of_file_names): def _all_lines(): for filename in list_of_file_names: with open(filename) as fpin: yield from enumerate(fpin) for nr, (fnr, line) in _all_lines: yield nr, fnr, line更复杂的 FNR、NR 和行数的 awk 行为如果 FNR、NR 和行数这三个你全都需要 , 仍然会有一些问题 。如果确实如此 , 则使用三元组(其中两个项目是数字)会导致混淆 。命名参数可使该代码更易于阅读 , 因此最好使用 dataclass:
import dataclass@dataclass.dataclass(frozen=True)class AwkLikeLine: content: str fnr: int nr: intdef awk_like_lines(list_of_file_names): def _all_lines(): for filename in list_of_file_names: with open(filename) as fpin: yield from enumerate(fpin) for nr, (fnr, line) in _all_lines: yield AwkLikeLine(nr=nr, fnr=fnr, line=line)你可能想知道 , 为什么不一直用这种方法呢?使用其它方式的的原因是总用这种方法太复杂了 。如果你的目标是把一个通用库更容易地从 awk 移植到 Python , 请考虑这样做 。但是编写一个可以使你确切地了解特定情况所需的循环的方法通常更容易实现 , 也更容易理解(因而易于维护) 。
理解 awk 字段一旦有了与一行相对应的字符串 , 如果要转换 awk 程序 , 则通常需要将其分解为字段 。Python 有几种方法可以做到这一点 。这将把行按任意数量的连续空格拆分 , 返回一个字符串列表:
line.split()如果需要另一个字段分隔符 , 比如以 : 分隔行 , 则需要 rstrip 方法来删除最后一个换行符:
line.rstrip("n").split(":")完成以下操作后 , 列表 parts 将存有分解的字符串:
parts = line.rstrip("n").split(":")这种拆分非常适合用来处理参数 , 但是我们处于 偏差一个的错误 场景中 。现在 parts[0] 将对应于 awk 的 $1 , parts[1] 将对应于 awk 的 $2 , 依此类推 。之所以偏差一个 , 是因为 awk 计数“字段”从 1 开始 , 而 Python 从 0 开始计数 。在 awk 中 , $0 是整个行 —— 等同于 line.rstrip("n") , 而 awk 的 NF(字段数)更容易以 len(parts) 的形式得到 。


推荐阅读