从 pycharm 中的重组文本文件中删除面包屑
Removing breadcrumbs from restructured text files in pycharm
我有大约 13,000 个文件需要从中删除面包屑。每个文件开头的模式大致如下所示:
Title
=====
| |image0| `link <link1.html>`__ |image1| ::
`link2 <link2.html>`__ ::
`link3 <link3.html>`__
| **Introduced** : VersionXXX
但是,在某些文件中,标题行和最后一行之间的部分是 2 或 4,具体取决于树的深度。无论标题行和此处显示的最后一行之间的行如何,我都希望完全删除中间部分。我不太清楚如何做到这一点,希望能得到一些帮助。我正在使用 pycharm 并且他们有一个正则表达式工具(我还没有成功),但我同样乐于使用 sed 或 python 等替代方法来遍历文件。
预期结果:
Title
=====
| **Introduced** : VersionXXX
感谢所有出色的解决方案。 最终解决方案以避免写入单独的文件:
import os
src_dir = '/PycharmProjects/docs/testfiles'
logf = open('failed_file_log.txt', 'w')
for filename in os.listdir(src_dir):
print(filename)
with open('{}/{}'.format(src_dir, filename), 'r') as f:
lines = f.readlines()
with open('{}/{}'.format(src_dir, filename), 'w') as f:
try:
for i in range(3):
f.write(lines[i])
copy = False
for line in lines:
if copy:
f.write(line)
elif line.startswith('| **Introduced**'):
copy = True
f.write(line)
except Exception as e:
logf.write('Failed to rewrite {}'.format(filename))
finally:
pass
这个表达式使用了三个捕获组,我们不需要的部分在第二个中,我们可以简单地替换它 (</code>)。 </p>
<pre><code>(.+\s*=====\s*)([\s\S]*)(\|\s+\*\*Introduced\*\* : .+)
Demo
测试
# coding=utf8
# the above tag defines encoding for this document and is for Python 2.x compatibility
import re
regex = r"(.+\s*=====\s*)([\s\S]*)(\|\s+\*\*Introduced\*\* : .+)"
test_str = ("Title\n"
"=====\n\n"
"| |image0| `link <link1.html>`__ |image1| ::\n"
" `link2 <link2.html>`__ ::\n"
" `link3 <link3.html>`__\n"
"| **Introduced** : VersionXXX")
subst = "\1\3"
# You can manually specify the number of replacements by changing the 4th argument
result = re.sub(regex, subst, test_str, 0, re.MULTILINE)
if result:
print (result)
# Note: for Python 2.7 compatibility, use ur"" to prefix the regex and u"" to prefix the test string and substitution.
由于您主要寻找的是固定模式,因此我会使用不带正则表达式的 Python 来复制文件。过程非常简单:复制前三行,然后跳过所有内容,直到到达 | **Introduced**
,然后复制其余部分。
with open('myfile.rst') as fin, open('myfile_out.rst') as fout:
for _ in range(3):
fout.write(next(fin))
copy = False
for line in fin:
if copy:
fout.write(line)
elif line.startswith('| **Introduced**'):
copy = True
fout.write(line)
将此代码片段应用于文件层次结构并将输出移回输入名称留作 reader.
的练习。
您可以使用 2 个捕获组并通过使用重复模式来匹配介于两者之间的内容,该模式使用否定先行检查每行是否以最后一行的模式开始 (?!
然后在替换中使用这两个组,在 python 中使用 re.sub
替换将是 r''
.
(\bTitle\n=+\n)(?:\n(?!\| \*\*).*)*(\n\| \*\*Introduced\*\* : Version.*)
说明
(\bTitle\n=+\n)
捕获第 1 组,匹配标题,换行符,1+ 次 +
和换行符
(?:
非捕获组
\n(?!\| \*\*).*
匹配换行符并断言直接在右边的内容不是 | **
使用否定前瞻。然后匹配 0+ 次除换行符之外的任何字符
)*
关闭非捕获组并重复0+次
(\n\| \*\*Introduced\*\* : Version.*)
捕获组 2,匹配换行符和匹配最后一行的模式
由于 OP 在问题中标记了 sed
,因此以下是两个 one-liner 以获得所需的结果:
sed -n '/Title/{N;N;p}; /Introduced/{p}' input
Title
=====
| **Introduced** : VersionXXX
或
awk
:
awk '/Title/{print;getline;print;getline;print}/Introduced/{print}' input
Title
=====
| **Introduced** : VersionXXX
sed
有它的用途,但它需要疯狂的技能才能根据需要进行 multi-line 处理。这是一种久经考验的 *nix 文本处理语言的替代方案,awk
;-)
**cleanup.awk**
#!/bin/awk -f
{
# print "dbg:[=10=]="[=10=]
}
/^$/{
print [=10=]
inside_unneeded=1;
}
{
if ([=10=] ~ /^\| \*\*Introduced\*\*/) {
print [=10=]
inside_unneeded=0
}
else if (! inside_unneeded) {
print [=10=]
}
你需要
chmod 755 cleanup.awk
和运行它作为
cleanup.awk file > file.new && /bin/rm file
如果您有能力保留备份(推荐),那么就&& mv file file.sav && mv file.new file
。或者您可以重定向到不同的目录,然后不必处理任何 &&
处理,即。 cleanup.awk file > /alt/path/for/new/data/file
。
将产生输出
Title
=====
| **Introduced** : VersionXXX
可能有一种方法可以使用 awk
shorthand 逻辑来显着减小此脚本的大小,但我将其置于一般 public 的可破译状态,熟悉 if/else if/else
类型逻辑。
所有块({ ... }
之间的代码)针对每一行输入执行,而以/^$/
开头的块仅针对空行处理。如果你有白色space在这些空行上,您需要 /^[ <tab>]*$/{
代替(不要键入 <tab>
,从键盘插入一个普通的 tab
字符)。
IHTH.
我有大约 13,000 个文件需要从中删除面包屑。每个文件开头的模式大致如下所示:
Title
=====
| |image0| `link <link1.html>`__ |image1| ::
`link2 <link2.html>`__ ::
`link3 <link3.html>`__
| **Introduced** : VersionXXX
但是,在某些文件中,标题行和最后一行之间的部分是 2 或 4,具体取决于树的深度。无论标题行和此处显示的最后一行之间的行如何,我都希望完全删除中间部分。我不太清楚如何做到这一点,希望能得到一些帮助。我正在使用 pycharm 并且他们有一个正则表达式工具(我还没有成功),但我同样乐于使用 sed 或 python 等替代方法来遍历文件。
预期结果:
Title
=====
| **Introduced** : VersionXXX
感谢所有出色的解决方案。 最终解决方案以避免写入单独的文件:
import os
src_dir = '/PycharmProjects/docs/testfiles'
logf = open('failed_file_log.txt', 'w')
for filename in os.listdir(src_dir):
print(filename)
with open('{}/{}'.format(src_dir, filename), 'r') as f:
lines = f.readlines()
with open('{}/{}'.format(src_dir, filename), 'w') as f:
try:
for i in range(3):
f.write(lines[i])
copy = False
for line in lines:
if copy:
f.write(line)
elif line.startswith('| **Introduced**'):
copy = True
f.write(line)
except Exception as e:
logf.write('Failed to rewrite {}'.format(filename))
finally:
pass
这个表达式使用了三个捕获组,我们不需要的部分在第二个中,我们可以简单地替换它 (</code>)。 </p>
<pre><code>(.+\s*=====\s*)([\s\S]*)(\|\s+\*\*Introduced\*\* : .+)
Demo
测试
# coding=utf8
# the above tag defines encoding for this document and is for Python 2.x compatibility
import re
regex = r"(.+\s*=====\s*)([\s\S]*)(\|\s+\*\*Introduced\*\* : .+)"
test_str = ("Title\n"
"=====\n\n"
"| |image0| `link <link1.html>`__ |image1| ::\n"
" `link2 <link2.html>`__ ::\n"
" `link3 <link3.html>`__\n"
"| **Introduced** : VersionXXX")
subst = "\1\3"
# You can manually specify the number of replacements by changing the 4th argument
result = re.sub(regex, subst, test_str, 0, re.MULTILINE)
if result:
print (result)
# Note: for Python 2.7 compatibility, use ur"" to prefix the regex and u"" to prefix the test string and substitution.
由于您主要寻找的是固定模式,因此我会使用不带正则表达式的 Python 来复制文件。过程非常简单:复制前三行,然后跳过所有内容,直到到达 | **Introduced**
,然后复制其余部分。
with open('myfile.rst') as fin, open('myfile_out.rst') as fout:
for _ in range(3):
fout.write(next(fin))
copy = False
for line in fin:
if copy:
fout.write(line)
elif line.startswith('| **Introduced**'):
copy = True
fout.write(line)
将此代码片段应用于文件层次结构并将输出移回输入名称留作 reader.
的练习。您可以使用 2 个捕获组并通过使用重复模式来匹配介于两者之间的内容,该模式使用否定先行检查每行是否以最后一行的模式开始 (?!
然后在替换中使用这两个组,在 python 中使用 re.sub
替换将是 r''
.
(\bTitle\n=+\n)(?:\n(?!\| \*\*).*)*(\n\| \*\*Introduced\*\* : Version.*)
说明
(\bTitle\n=+\n)
捕获第 1 组,匹配标题,换行符,1+ 次+
和换行符(?:
非捕获组\n(?!\| \*\*).*
匹配换行符并断言直接在右边的内容不是| **
使用否定前瞻。然后匹配 0+ 次除换行符之外的任何字符
)*
关闭非捕获组并重复0+次(\n\| \*\*Introduced\*\* : Version.*)
捕获组 2,匹配换行符和匹配最后一行的模式
由于 OP 在问题中标记了 sed
,因此以下是两个 one-liner 以获得所需的结果:
sed -n '/Title/{N;N;p}; /Introduced/{p}' input
Title
=====
| **Introduced** : VersionXXX
或
awk
:
awk '/Title/{print;getline;print;getline;print}/Introduced/{print}' input
Title
=====
| **Introduced** : VersionXXX
sed
有它的用途,但它需要疯狂的技能才能根据需要进行 multi-line 处理。这是一种久经考验的 *nix 文本处理语言的替代方案,awk
;-)
**cleanup.awk**
#!/bin/awk -f
{
# print "dbg:[=10=]="[=10=]
}
/^$/{
print [=10=]
inside_unneeded=1;
}
{
if ([=10=] ~ /^\| \*\*Introduced\*\*/) {
print [=10=]
inside_unneeded=0
}
else if (! inside_unneeded) {
print [=10=]
}
你需要
chmod 755 cleanup.awk
和运行它作为
cleanup.awk file > file.new && /bin/rm file
如果您有能力保留备份(推荐),那么就&& mv file file.sav && mv file.new file
。或者您可以重定向到不同的目录,然后不必处理任何 &&
处理,即。 cleanup.awk file > /alt/path/for/new/data/file
。
将产生输出
Title
=====
| **Introduced** : VersionXXX
可能有一种方法可以使用 awk
shorthand 逻辑来显着减小此脚本的大小,但我将其置于一般 public 的可破译状态,熟悉 if/else if/else
类型逻辑。
所有块({ ... }
之间的代码)针对每一行输入执行,而以/^$/
开头的块仅针对空行处理。如果你有白色space在这些空行上,您需要 /^[ <tab>]*$/{
代替(不要键入 <tab>
,从键盘插入一个普通的 tab
字符)。
IHTH.