拆分配置文件中的文本

Split up the text in configuration files

我正在尝试编写一个简单的配置文件解析器,例如,可能有一个名为 INCAR:

的文件
NSW    = 1000
POTIM  = 1
TEBEG  = 300

如果我想提取 POTIM 的值,我可以采用 awk 提取空白之间的文本并使用以下脚本:

#!/bin/bash

vaspT () 
{ 
    if [ -f INCAR ]; then
        local potim=$(grep POTIM INCAR | awk '{print }');
    else
        local potim=1;
    fi;
    echo "# Time step: ${potim}" > .vasp_md.dat;
    echo "# Step  Temperature Total_energy E_pot E_kin" >> .vasp_md.dat;
}

vaspT

但如果有人不遵守对齐规则,并使用配置文件,如:

NSW = 1000
POTIM=1
TEBEG=300

然后,我必须使用另一个分隔符。
我的问题是:
对于这种工作,是否有简单的解决方案或现有的库(Python 或 Bash 是可以接受的)?

在这种情况下您可以使用正则表达式:

import re
myString = """
NSW = 1000
POTIM       =          1
TEBEG=300
"""
re.findall("POTIM(\s+)?\=(\s+)?(\d+)", myString)

输出

[('       ', '          ', '1')]

如果您将正则表达式与此模式一起使用,无论有多少空格,元组的最后一个元素(如果有的话)始终是您想要的变量。

另一个例子

import re
myString = """
NSW = 1000
POTIM=1
TEBEG=300
"""
re.findall("POTIM(\s+)?\=(\s+)?(\d+)", myString)

输出

[('', '', '1')]

对于这个用例,我会使用 cut...

grep POTIM INCAR | cut -d "=" -f 2 | sed s/\ //g

  • cut -d "=" -f 2 将采用与 = 分隔符相关的第二个字段。

  • sed s/\ //g 将删除值周围的空格

您可以使用 awk 并将字段分隔符设置为可选空格之间的 =

如果第一个字段是 POTIM,则打印第二个字段。

awk -F"[[:space:]]*=[[:space:]]*" '
=="POTIM" {print }
' file

输出

1

您可以创建一个包含变量及其值的字典:

import re

with open("filename", "r") as f:
    config = dict(re.findall(r"(\w+)\s*=\s*(\w+)", f.read()))
print(config)

输出:

{'NSW': '1000', 'POTIM': '1', 'TEBEG': '300'}

然后您可以轻松检索每个变量的值:

print(config["POTIM"])  # 1

(\w+)\s*=\s*(\w+)
  • (\w+):第一个捕获组,匹配1到无限次之间的任意单词字符。
  • \s*:匹配0到无限次之间的任何空格。
  • =:匹配 =.
  • \s*:匹配0到无限次之间的任何空格。
  • (\w+):第二个捕获组,匹配1到无限次之间的任何单词字符。

对于每个匹配项,re.findall 将创建一个包含捕获组的元组。然后使用 dict() 会将列表转换为字典。

使用sed

#!/bin/bash

vaspT () 
{ 
    if [ -f INCAR ]; then
        local potim
        potim=$(sed -n '/POTIM/s/.*=[[:space:]]\?\(.*\)//p' INCAR)
    else
        local potim
        potim=1
    fi
    echo "# Time step: ${potim}" > .vasp_md.dat
    echo "# Step  Temperature Total_energy E_pot E_kin" >> .vasp_md.dat
}

vaspT

Is there a simple solution or an existing library(...)Python(...)for this kind of job?

python标准库中有configparser,但它假设总是有header,所以如果你的文件没有,你需要添加一个,考虑如下例如,让 file.txt 内容为

ZERO=0
LEFT =1
RIGHT= 1
BOTH = 2
MULTI  =   3

那么可以这样使用

import configparser
config = configparser.ConfigParser()
with open("file.txt","r") as f:
    config.read_string('[default]\n'+f.read())
print(config['default']['ZERO']) # 0
print(config['default']['LEFT']) # 1
print(config['default']['RIGHT']) # 1
print(config['default']['BOTH'])  # 2
print(config['default']['MULTI'])  # 3

说明:我添加了默认行以允许 configparser 工作。请注意,此变通办法和您可能会选择强制用户使用 headers 而不是采用此变通办法,在这种情况下使用变得更容易:

import configparser
config = configparser.ConfigParser()
config.read("file.txt")
...

您在 shell 中做的太多了。 awk 是发明 shell 的人也为 shell 发明的用来调用操作文本的工具,因此只需使用 awk 进行整个文本操作,而不是不必要地添加其他 shell 命令来提供 awk一次一行,等等

如果文件存在但不包含 POTIM= 行或包含多个 POTIM= 行或如何处理文件中的注释(或它们的外观),您的问题没有告诉我们该怎么做所以忽略注释的可能性并猜测如果 POTIM= 不存在你想打印 1 而如果它确实存在你想打印最后看到的值:

$ cat tst.sh
#!/usr/bin/env bash

vaspT() {
    local infile='INCAR'
    [[ -f "$infile" ]] || infile='/dev/null'

    awk '
        {
            gsub(/^[[:space:]]+|[[:space:]]+$/,"")
            tag = val = [=10=]
            sub(/[[:space:]]*=.*/,"",tag)
            sub(/[^=]*=[[:space:]]*/,"",val)
            tag2val[tag] = val
        }
        END {
            print "# Time step:", ("POTIM" in tag2val ? tag2val["POTIM"] : 1)
            print "# Step  Temperature Total_energy E_pot E_kin"
        }
    ' "$infile" > .vasp_md.dat
}

vaspT

$ ./tst.sh

$ cat .vasp_md.dat
# Time step: 1
# Step  Temperature Total_energy E_pot E_kin

我用这个:

{
    gsub(/^[[:space:]]+|[[:space:]]+$/,"")
    tag = val = [=13=]
    sub(/[[:space:]]*=.*/,"",tag)
    sub(/[^=]*=[[:space:]]*/,"",val)
    tag2val[tag] = val
}

而不仅仅是:

BEGIN { FS = "[[:space:]]*=[[:space:]]*" }
{ tag2val[] =  }

所以如果行中有前导或尾随空格或者值包含 =,代码将继续工作,例如:

NSW    = 1000
   POTIM  = "foo=bar"  
TEBEG  = 300