王美洁

1.14 批处理脚本:for

这一节最重要的不是语法本身,而是一个意识:

如果你发现自己在重复做同一类操作,十有八九可以写成 for 循环。

我这里不想只讲一个“教科书式”的最小例子,而是直接结合一些真实会出现的命令来讲。

因为对科研计算来说,for 循环最常见的用途不是刷算法题,而是:

  • 对很多个目录做同一件事
  • 对很多个文件做同一件事
  • 批量提取结果
  • 批量重命名

1. 最小结构先看懂

最基本的样子是:

bash
for i in *; do
  echo $i
done

你可以先这样理解:

  • for i in ...:把一批对象一个个取出来
  • i:当前这个对象的名字
  • do ... done:对每个对象执行同样的操作

如果是:

bash
for i in {1..5}; do
  echo $i
done

那就是依次处理:

  • 1
  • 2
  • 3
  • 4
  • 5

2. 最常见:对每个目录复制或移动文件

这一类在科研里非常高频。

例如你有很多个计算目录,想把每个目录下的 CONTCAR 统一复制出来:

bash
for i in *; do
  cp $i/CONTCAR zzz/$i.vasp
done

这条命令的意思是:

  • 遍历当前目录下的每个子目录 i
  • $i/CONTCAR 复制出来
  • 并命名成 zzz/$i.vasp

这类操作很适合:

  • 批量收集结构
  • 批量整理输出文件
  • 批量汇总结果

你历史里还有很多同类例子,例如:

bash
for i in */; do
  cp INCAR KPOINTS POTCAR $i
done

这类写法就是:

  • 对每个计算目录统一分发输入文件

3. 第二类:批量重命名

这也是 for 循环最常见的场景之一。

例如:

bash
for i in *; do
  mv $i ${i/COOH_m_}
done

这条命令的意思可以先粗略理解成:

  • 遍历当前目录下的每个文件或目录
  • 把名字里的 COOH_m_ 去掉
  • 再用新名字重命名

同类的还有:

bash
for i in *; do
  mv $i ${i/.vasp}
done

这类通常是在:

  • 去掉某个前缀
  • 去掉某个后缀
  • 把旧命名改成新命名

所以你会发现,前一节讲的字符串处理,在这里就真正派上用场了。

4. 第三类:批量提取结果

这类操作非常像“手工抄表”,只是交给 shell 去做。

例如:

bash
for i in *; do
  echo -n $i,
  tail -n 1 ./$i/OSZICAR | awk '{print $5}'
done

这条命令是在做:

  • 先输出目录名
  • 再取每个目录里 OSZICAR 的最后一行
  • 然后用 awk 取其中一列

结果通常就会变成一行一行的:

text
dir1, ...
dir2, ...
dir3, ...

这就是很典型的:

  • 批量取能量
  • 批量取磁矩
  • 批量生成 csv 风格日志

你历史里还有更完整的写法:

bash
for i in *; do
  echo -n $i,
  tail -n 1 ./$i/OSZICAR | awk '{print $5}'
done > ../quad.csv

也就是把结果直接写进一个汇总文件。

5. 第四类:从给定列表中批量处理

有时候你不是要遍历所有文件,而是只处理一小部分指定对象。

例如:

bash
for i in 0.12_-0.02_90 0.12_0.02_90 0.10_0.02_90; do
  mv $i.vasp sec
done

这条命令的思路是:

  • 明确写出一组名字
  • 只处理这几个对象

这类很适合:

  • 手动挑出一批异常点
  • 单独整理一组结果
  • 对少数几个结构做额外操作

另一类很常见的是从文件里读列表:

bash
for i in $(cat sec); do
  rm $i.vasp
done

这类写法你一定会经常见到,但也要先知道:

  • 它简单
  • 但如果名字里有空格,会有坑

所以新手阶段先会看懂它就够了。

6. 第五类:嵌套循环

当目录结构再复杂一点时,就会出现“目录里还有很多子目录”的情况。

例如:

bash
for j in *; do
  cd $j
  for i in *.vasp; do
    echo -n $j,${i/.vasp},
    head -n 1 $i | awk '{print $2}'
  done
  cd ..
done

你可以先这样理解:

  • 外层循环 j:遍历一级目录
  • 内层循环 i:遍历每个一级目录里的文件

所以嵌套循环适合处理:

  • 多层目录
  • 多组结构
  • 每个体系下面还有很多子任务

当然,这种写法也更容易出错,尤其是里面夹着 cd 的时候。

所以对新手来说,先做到:

  • 看懂嵌套循环在干什么
  • 写的时候保持结构清楚

这就已经够好了。

7. 一个很实用的建议

你历史里的很多命令都说明了一件事:

for 循环最有价值的地方,不是语法,而是把重复劳动压成一条命令。

但也正因为它威力很大,所以更要小心。

特别是涉及:

  • rm
  • mv
  • sed -i

这类会直接改文件的命令时,建议你先做两步:

第一步:先 echo

bash
for i in *; do
  echo mv $i ${i/.vasp}
done

先不要真执行,只把将要执行的命令打印出来看看对不对。

第二步:确认没问题再去掉 echo

这比直接一把梭安全得多。

8. 这一节最少要做到什么

  1. 知道 for ... do ... done 是批处理循环
  2. 看懂“对每个目录复制文件”这种最常见写法
  3. 看懂“批量重命名”这种最常见写法
  4. 看懂“批量提取结果”这种最有科研味的写法
  5. 知道危险命令最好先 echo 预演

下一篇

继续看 1.15 终端环境优化