拆分配置文件中的文本
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
我正在尝试编写一个简单的配置文件解析器,例如,可能有一个名为 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