实用主义的Linux命令和Bash脚本基础入门(更新中)
实用主义的Linux命令和Bash脚本基础入门(更新中)

实用主义的Linux命令和Bash脚本基础入门(更新中)

实用主义的Linux命令和Bash脚本基础入门(更新中)
Pragmatical Tutorial for Basic Linux Commands and Bash Scripts (in Updating)

Jiawei Xu, Yingqi Li
Released: 2023-10-10 / Updated: 2023-10-10

Linux系统及其自带的Bash语言是计算化学科研工作中必须掌握的基本技能。本文旨在让计算化学初学者快速熟悉Linux系统和Bash语言,从而能够尽快开展相关工作,节省时间,少走弯路。同时本文也介绍了Linux系统下安装软件、队列系统使用、监控任务等的一般做法。

1. Linux系统的基本架构

1.1 Linux系统的文件结构

不同于Windows系统常见的分盘架构,Linux系统采用树状结构。Linux采用的是一个层级式的文件系统结构,这种结构以根目录“/”为基础(所以一定不能尝试运行rm -rf /,这代表强制递归删除根目录,也就是把整个系统都删掉,里面的数据自然也全没了!),所有其他文件和目录都是从这里开始延伸。这种架构相比于Windows更加有条理。

1.2 文件路径

可以以绝对路径和相对路径两种办法来定位某一文件。

1.2.1 绝对路径

绝对路径(absolute path)指从根目录开始描述路径,例如“/hitachi/mako/is/my/waifu.py”就是waifu.py这个python脚本的绝对路径。

1.2.2 相对路径

相对路径(relative path)指以某一特定文件夹为起点开始描述路径。常见的参照物有,当前目录“.”,上一目录“..”,当前用户的/usr目录“~/”。例如“../play/genshin.sh”代表上一目录中的/play目录下的genshin.sh脚本。又例如“./arknights.sh”代表当前目录下的arknights.sh脚本。

2. Linux系统必会技能

2.2 grep (Globally search a Regular Expression and Print) 正则表达式全局搜索与输出
基本用法:grep命令用于对文本进行搜索,并默认输出匹配行。

grep "SCF Done:" test.log # 从Gaussian16输出文件中查找包含“SCF Done:”的所有行并输出。
grep "FINAL SINGLE POINT ENERGY" test.out # 从ORCA输出文件中查找包含“FINAL SINGLE POINT ENERGY”的所有行并输出。
grep E(CASSCF) test.out # 如果目标字符串不含有空格,也可以省略引号。

一些扩展案例:

grep "SCF Done:" test.log | tail -1 # 从Gaussian16输出文件中查找包含“SCF Done:”的所有行,tail控制只输出最后一次匹配的结果。
grep "Frequencies --" freq.log | head -1 # head控制只输出第一次匹配的结果。
grep "SCF Done:\|EUMP2" test.log # 查找包含“SCF Done:”或“EUMP2”的所有行。其中“|”用于并列多个关键词,“\”是转义符。

使用set将grep命令的输出按空格分割,逐单词存储。以下两行命令即可从Gaussian16的输出文件(如优化任务)中获取最后一次SCF迭代的能量结果:

set `grep "SCF Done:" test.log | tail -1` # 反引号(``)中的内容将会以命令形式执行,并将输出结果传递给set,取消屏幕输出。
echo $5 # echo表示输出,默认输出到屏幕。此处输出grep结果的第五个单词,对应于SCF能量。

类似地,以下命令可以读取最小的一个频率,可用于检查虚频:

set `grep "Frequencies --" freq.log | head -1`
echo $3
set `grep "Frequencies --" freq.log | head -1`; echo $3 # 用分号分隔,可在同一行中书写多行命令。

2.3 sed(Stream EDitor)流编辑器

sed最重要的功能是对文件进行操作,如插入、删除和查找替换。

# 插入
sed -i "1i\%nprocshared=32" test.gjf # 在第1行前插入(i),直接对文件进行操作(-i)。“\”仅为了便于观看,可省略,此处不起实际作用。
sed -i "1a\%nprocshared=32" test.gjf # 在第1行后插入(a)。
sed -i "/%chk/i %nprocshared=32" test.gjf # 查看包含“%chk”的行,在其前插入“%nprocshared=32”。
sed -i "1i\%nprocshared=32\n%mem=96GB" test.gjf # 插入连续多行,“\n”表示换行符。
# 删除
sed -i "/nproc/d" test.gjf # 删除(d)包含“nproc”的所有行。
sed -i "1d" test.gjf # 删除第1行。
# 查找替换
sed -i "s/nproc=2/nproc=32/g" test.gjf # 查找所有的“nproc=2”字段,并将其替换为“nproc=32”。
sed -i "/nproc/c\%nprocshared=32" test.gjf # 将包含“nproc”的整行替换(c)为“%nprocshared=32”

实例练习 现有一批Multiwfn产生的.gjf文件,需将其修改为MOKIT做GKS-EDA计算(实际上是生成XEDA输入文件)的输入文件。结构中包含1个Fe原子和H、C、N、O组成的有机配体,将Fe原子单独划分为一个片段,其余配体作为另一个片段。试实现之。

分析 需要实现以下几个功能:1. 根据实际需求设定并行核数和内存;2. 删除“%chk”行;3. 修改关键词行;4. 将Multiwfn产生.gjf的默认标题替换为“{gks}”;5. 修改电荷和自旋多重度;6. 按照片段划分规则对原子排序,并附上所属片段序号。

实现 首先考虑对其中一个输入文件完成以上工作。


3. Bash脚本基础

3.1 循环语句

3.1.1 for循环
for循环是最基本的循环语句,其基本结构为:for variable in range; do commands; done,其依次将range中的每个值赋值给循环变量variable,为其执行循环体commands所定义的命令。例如以下语句将会输出10次“Hello World!”:

for i in {1..10} # 与Python不同,Bash中的{1..10}是一个闭区间
do
  echo "Hello World!"
done

Bash脚本对缩进不敏感,但为保证代码的可读性,也应合理安排缩进。以上命令较为简单,也可写成单行的形式:

for i in {1..10}; do echo "Hello World!"; done

更多的时候,循环体内部需要对循环变量的值进行引用:

for i in {1..10}; do echo $i; done # $i表示变量i的值,该命令将会逐行输出1到10共10个数字。
for i in ./*.gjf; do g16 $i; done # 变量也可以是文件名等,该命令为当前目录下所有.gjf后缀的文件运行g16任务。

结合其他Linux命令,或调用外部程序,以实现更多样化的功能:

for i in ./*.gjf
do
  filename=`basename $i .gjf` # 将i的值去掉后缀.gjf,并赋值给新的变量filename。
  mkdir -p $filename; mv $i $filename; cd $filename # 为每个任务创建单独的文件夹运行。
  g16 $filename.gjf
  cd ..
done

以下命令将会实现批量转换当前目录下的所有.fchk文件为.gjf文件:

for i in ./*.fchk
do
  Multiwfn $i >> tmp << EOF # >>将Multiwfn的屏幕输出重定向至临时文件tmp;<<将后续内容重定向至Multiwfn执行。
100
2
10

n
0
q
EOF # EOF: End Of File;两个EOF之间的所有内容将被重定向至Multiwfn。
  rm tmp # 删除临时文件。
done

4. 本组常用软件安装

在不使用管理员权限的情况下,Linux系统下软件或程序的安装,一般根据安装包的不同分为预编译版本安装和从源码安装。预编译版本的程序安装一般非常简单,解压后根据实际情况设置环境变量即可,如Gaussian16、ORCA等都提供预编译版本安装包。从源码安装的情况更为复杂,通常分为三步:
1). 配置 (configure)
许多程序对应于一个可执行文件configure,一般是已经具有可执行权限的Bash脚本。其基本命令是:

./configure --prefix=/home/jwxu/programs/install_dir

选项“–prefix”指定安装目录。某些程序(如Dalton)强制要求安装目录不能与编译目录雷同,即安装目录必须与解压缩的得到目录不同,例如解压缩Dalton到~/programs/dalton-src,则安装目录必须是与之不同的~/programs/dalton等。一般总是建议使用不同的安装目录,安装完成后可以删去编译目录节省空间,但有些程序显然不能这么做,如Tcl-ChemShell,这是开发者的问题。
有时还需要提供额外的选项,根据实际情况进行处理。
2). 编译 (compile)
一般使用make命令进行编译。配置完成后会在当前目录产生一个Makefile文件,make命令根据Makefile的内容进行编译,可通过以下方式并行编译加快速度:

make -j # 使用全部可用线程数。
make -j16 # 使用16线程编译。

某些情况下并行编译可能会出错,此时应先去掉-j选项尝试进行串行编译。编译完成后,相关可执行程序和库文件已经存在于编译目录内的各个子文件夹中。
3). 安装 (install)
如程序是通过make编译的,则运行以下命令安装:

make install [-j]

之后相关文件被安装至配置时指定的安装目录。此时可删去编译目录以节省空间。在.bashrc或队列系统脚本中添加相关环境变量,就可以正常使用程序了。
大多数程序都是通过以上三步进行安装,但并不都是使用以上命令,如Dalton通过cmake编译,某些Python程序会提供setup.py用于安装,这在后面遇到时再进行详细解释。

4.1 Intel全家桶

Intel全家桶,一般指的是包含Intel编译器、MKL数学库、Intel MPI并行库等常用工具在内的一系列Intel产品的代称,许多常用软件在编译或运行时都会用到。以在线版为例(如机子无法联网,则下载离线版),首先到Intel官网(https://www.intel.cn/content/www/cn/zh/homepage.html)下载所需版本的Intel oneAPI Base Toolkit和Intel oneAPI HPC Toolkit,然后运行安装程序:

chmod +x l_BaseKit_p_2022.2.0.262.sh l_HPCKit_p_2022.2.0.191.sh
./l_BaseKit_p_2022.2.0.262.sh # 按照提示进行安装。默认安装在~/intel目录下。
./l_HPCKit_p_2022.2.0.191.sh
cd ~/intel/oneapi
./modulefiles-setup.sh # 此时在~/intel/oneapi/modulefiles目录下产生了相应环境文件。

安装tcl和modules环境管理器(如已安装,可跳过此步骤)。有些情况下,在登陆节点已经安装了tcl,然而计算节点上并未安装,导致modules运行时报错找不到解释器“/usr/bin/tclsh”,对于非root用户可自行安装tcl来保证modules正常运行。注意,如果使用集群或超算,应向管理员咨询是否已经安装了相关环境,以免重复安装。

wget https://cfhcable.dl.sourceforge.net/project/tcl/Tcl/8.6.9/tcl8.6.9-src.tar.gz
tar -xf tcl8.6.9-src.tar.gz
cd tcl8.6.9/unix
./configure --prefix=/home/jwxu/programs/tcl-8.6.9
make -j && make install -j # 将tcl安装到/home/jwxu/programs/tcl-8.6.9

wget https://newcontinuum.dl.sourceforge.net/project/modules/Modules/modules-4.2.4/modules-4.2.4.tar.gz
tar -xf modules-4.2.4.tar.gz
cd modules-4.2.4
./configure --prefix=/home/jwxu/programs/modules \
            --with-tcl-lib=/home/jwxu/programs/tcl-8.6.9/lib \
            --with-tcl-inc=/home/jwxu/programs/tcl-8.6.9/include \
            --with-tclsh=/home/jwxu/programs/tcl-8.6.9/bin/tclsh # 也可能是tclsh8.6
make -j # 此步骤可能会报错“未定义的...”,注释掉相关代码即可。
make install -j # 将modules安装到/home/jwxu/programs/modules

cp -r /home/jwxu/intel/oneapi/modulefiles/* /home/jwxu/programs/modules/modulefiles
# 以上将Intel相关的modules环境文件复制到默认路径。也可用以下命令替代:
export MODULEPATH=$MODULEPATH:/home/jwxu/intel/oneapi/modulefiles # 如用此法,可将此行命令添加至~/.bashrc文件。

source /home/jwxu/programs/modules/init/profile.sh # 可将此行命令添加至~/.bashrc文件。
module load compiler mkl mpi # 即载入Intel编译器、MKL数学库和Intel MPI并行库。

3.2 Tcl-ChemShell 3.7.1

首先安装Tcl,参见3.1中相关内容。使用Intel编译器安装串行版本的Tcl-ChemShell:

export CC=icc
export F77=ifort
export F90=ifort
export TCLROOT=/home/jwxu/programs/tcl-8.6.9
export LIBTCL=$TCLROOT/lib/libtcl8.6.so
export PATH=/home/jwxu/programs/chemsh-3.7.1/bin:/home/jwxu/programs/chemsh-3.7.1/scripts:$PATH
cd chemsh-3.7.1/src/config
./configure

添加环境变量,或将环境变量写入投任务的脚本中:

export TCLROOT=/home/jwxu/programs/tcl-8.6.9
export LIBTCL=$TCLROOT/lib/libtcl8.6.so
export PATH=$TCLROOT/bin:/home/jwxu/programs/chemsh-3.7.1/bin:/home/jwxu/programs/chemsh-3.7.1/scripts:$PATH

4.3 GAMESS-US

5. 队列和进程

5.1 队列系统

5.1.1 队列系统简介

5.2 进程监控

常用的进程监控命令是top和ps u,二者均会打印当前系统运行的各个进程以及唯一对应的PID号。对于PC机或未安装队列系统的小型服务器,可使用这两个命令实现进程监控,当某一进程结束后,执行后续进程。例如:

#!/bin/bash
flag=1
while [ "$flag" -eq 1 ]
do
  sleep 1m # 等待1分钟。
  PID=$1
  PID_EXIST=$(ps u | awk '{print $2}' | grep -w $PID) # 判断当前是否存在该进程。
  if [ ! $PID_EXIST ]
  then
    mpirun -np 24 vasp
    flag=0
  else
    echo "Previous job unfinished. Continue waiting for 1 min..."
  fi
done

假设该脚本为./monitor.sh。首先通过ps u命令查看需要被监控的进程所对应的PID号,运行./monitor.sh PID,该脚本会在后台每隔1分钟检查前序任务是否结束,当被监控的进程结束后,该脚本便会执行“mpirun -np 24 vasp”。同时,该脚本本身对应于一个“/bin/bash monitor.sh”的进程,自然也是可以被监控的,由于“mpirun -np 24 vasp”是该进程的子进程,./monitor.sh进程会在VASP运行结束后结束。虽然不知道后续VASP任务的进程号,但通过监控./monitor.sh的进程号,也就实现了对后续VASP任务的监控。