王美洁

1.13 文本处理

一些很多日常操作,其中有典型的两类:

  • 从文件里提取你想要的信息
  • 修改文件里的内容

当然你可以古法的,一个个 cat 复制,收集或者一个个 vim 修改...

如果你忍受了这种无聊重复的操作,可以试试两个命令:

  • awk
  • sed

1. awk

你可以先把 awk 理解成:

很适合从结构化文本里按列、按模式提取信息。

在计算科研里,一个很常见的场景就是从很多输出文件里批量提取你关心的内容。

例如做一组自旋极化计算后,你可能想从很多个目录里的 OSZICAR 最后一行提取:

  • E0
  • mag
bash
awk 'END{print $5, $10}' */OSZICAR # 例如从每个 OSZICAR 最后一行提取 E0  mag 所在列

你可以先这样理解:

  • END{print}:只在文件最后执行一次
  • $5, $10:取这一行的第 5 列和第 10 列
  • */OSZICAR:一次处理很多个目录下的 OSZICAR

很多时候你最关心的不是前面的每一步,而是最后一步的信息。

如果你不确定 E0mag 在第几列,可以先粗暴一点:

bash
awk 'END{print}' */OSZICAR # 先把每个 OSZICAR 最后一行都打出来

这类写法很适合:

  • 从很多个结果目录里批量提取最后一步结果
  • 快速对比不同结构、不同参数下的 E0mag
  • 不想一个个打开 OSZICAR

很多时候,最有用的不是“把整份文件读完”,而是先快速抓出:

  • 最后一行
  • 某几列数字

所以 awk 的核心价值,可以先理解成:

  • 先筛出你关心的行
  • 再取你关心的列

2. sed:更适合替换

你可以先把 sed 理解成:

很适合把文本里的某些内容批量替换掉。

在科研里,一个很常见的场景就是做 benchmark 时批量改 INCAR 参数。

例如你想测试一组不同的 ENCUT

  • ENCUT = 400
  • ENCUT = 450
  • ENCUT = 500
  • ENCUT = 550

这时你通常会复制出很多个目录,然后在每个目录里把 INCAR 里的 ENCUT 改掉。

最核心的替换命令其实就是:

bash
sed 's/ENCUT = 400/ENCUT = 450/g' INCAR #  ENCUT  400 换成 450,并输出到屏幕

这里最常见的结构是:

bash
sed 's/旧内容/新内容/g' 文件名

可以先这样理解:

  • s = substitute,替换
  • 第一个 /.../:旧内容
  • 第二个 /.../:新内容
  • g = global,这一行里能替换的都替换

但这里要注意:

bash
sed 's/ENCUT = 400/ENCUT = 450/g' INCAR

默认只是把结果打印到终端,不会直接改文件本身。

如果你想直接修改文件,可以用:

bash
sed -i '' 's/ENCUT = 400/ENCUT = 450/g' INCAR # macOS 直接改文件
sed -i 's/ENCUT = 400/ENCUT = 450/g' INCAR    # Linux 直接改文件

这一点要格外小心,因为一旦用 -i,就是直接改文件了。