![如何删除文本文件中两个关键字之间的文本](https://linux22.com/image/197190/%E5%A6%82%E4%BD%95%E5%88%A0%E9%99%A4%E6%96%87%E6%9C%AC%E6%96%87%E4%BB%B6%E4%B8%AD%E4%B8%A4%E4%B8%AA%E5%85%B3%E9%94%AE%E5%AD%97%E4%B9%8B%E9%97%B4%E7%9A%84%E6%96%87%E6%9C%AC.png)
上下文:GNU/Linux Ubuntu。
我有一个由数千行组成的文件,我想要一个脚本来删除两个特定关键字之间的一些行。
初始文件如下:
bla bla
...
bla bla
keyword1
bla bla
...
bla bla
keyword2
bla bla
...
bla bla
我想保留除keyword1
和之间的部分之外的所有文件keyword2
。
让我们考虑一下,keyword1
并keyword2
在文件中只出现一次;这些关键字的行前或后可能有其他字符,例如空格或<
或>
带有关键字的行看起来像这样(它们实际上是基于 XML 的文件):
<keyword2>
关键字可以保留在文件中,也可以与随附的文本一起删除,我对这两种结果都满意。
我不知道如何继续使用grep
.我不熟悉awk
;它能成功吗?
答案1
我将您的示例文本放入文件中file
,并使用<>
关键字进行了测试。
该命令sed
删除关键字
$ < file sed '/keyword1/,/keyword2/d'
bla bla
...
bla bla
bla bla
...
bla bla
该命令sed
保留关键字
$ < file sed -n -e '1,/keyword1/p' -e '/keyword2/,$p'
bla bla
...
bla bla
<keyword1>
<keyword2>
bla bla
...
bla bla
答案2
使用 Raku(以前称为 Perl_6)
raku -ne '.put unless /keyword1/ ^fff^ /keyword2/;'
输入示例:
bla bla
...
bla bla
keyword1
bla bla
...
bla bla
keyword2
bla bla
...
bla bla
示例输出:
bla bla
...
bla bla
keyword1
keyword2
bla bla
...
bla bla
简而言之,Raku 的-ne
命令行标志告诉 Raku 在不自动打印的情况下执行代码。打印是通过第一条指令.put
(“print-using-terminator”,即换行符)完成的。.
前面的点是其中的put
缩写,表示主题变量(在本例中,包含来自输入行的数据)。$_.put
$_
该fff
指令是 Raku 类似 sed 的“触发器”运算符,它根据两个周围的正则表达式打开/关闭。在 Raku(和 Perl5)中,unless
是if not
.最后,^
周围的插入符号fff
告诉^fff^
Raku 排除端点。
因为unless
是一个否定,^fff^
否定端点的排除,从而在输出中保留keyword1
和。keyword2
使用fff
而不是从输出中^fff^
删除keyword1
和。keyword2
(这里请注意,如果您确实想解析一个XML
文件,那么可以使用 Raku 的模块制作单行 Raku 解决方案XML
)。
https://unix.stackexchange.com/search?q=Raku+%5BXML%5D
https://github.com/raku-community-modules/XML
https://raku.org
答案3
当“关键字”不是行中唯一的单词时,先前关于 sed 的建议不会给出预期结果。如果你想从任何段落中的任意两个单词之间提取文本,无论它们的位置如何,你将需要 Perl,特别是Perl 的文件读取
例如,假设我们有这样的文本:
Sir Arthur Conan Doyle was born on May 22, 1859, in Edinburgh.
He studied medicine at the University of Edinburgh and began to write stories while he was a student.
Over his life he produced more than 30 books, 150 short stories, poems, plays, and essays across a wide range
of genres.
His most famous creation is the detective Sherlock Holmes, who he introduced in his first novel, A Study in Scarlet (1887).
This was followed in 1889 by an historical novel, Micah Clarke.
我这里的关键词分别是“医学”和“福尔摩斯”。
sed 的结果将准确删除段落中的第一行和最后一行。而预期的结果还应该删除句子中之前和包含的部分medicine
,加上之后和包含的部分Holmes
。
让我们试试 Perl 的 File Slurp:
perl -0777 -i -pe 'push @a,/medicine(.*?)Holmes/s;END{print "@a"}' myparagraph.txt
输出:
at the University of Edinburgh and began to write stories while he was a student.
Over his life he produced more than 30 books, 150 short stories, poems, plays, and essays across a wide range
of genres.
His most famous creation is the detective Sherlock
答案4
由于我们没有真正的 XML 文档可供使用,我将假设相关文档类似于以下内容:
<?xml version="1.0"?>
<root>
<entry>
<name>Joe</name>
<number>133</number>
</entry>
<entry>
<name>Mary</name>
<number>123</number>
</entry>
<entry>
<name>Stan</name>
<number>233</number>
</entry>
</root>
任务也有点不清楚,所以我将展示如何
entry
删除给定值的节点之一name
。- 给定一个值,更改
number
一个节点的值。entry
name
entry
给定一个值,删除其中一个节点的内容name
。
首先使用相当常见的命令行 XML 解析器来完成此操作xmlstarlet
,然后使用鲜为人知的xq
(来自https://kislyuk.github.io/yq/),著名的 JSON 解析器的包装器jq
。
首先,使用 XPath 语法xmlstarlet
:
删除斯坦:
xmlstarlet ed \ --var name '"Stan"' \ --delete '//entry[name = $name]' file.xml
这将获取 XPath 字符串
"Stan"
,将其分配给内部变量$name
,并使用它来挑选entry
具有该特定name
值的节点。该entry
节点可能位于文档中的任何位置,因为我们使用//entry
而不是特定的路径来/root/entry
查找它。找到的节点将被删除
xmlstarlet
,并将生成的 XML 文档写入标准输出。生成的文档:
<?xml version="1.0"?> <root> <entry> <name>Joe</name> <number>133</number> </entry> <entry> <name>Mary</name> <number>123</number> </entry> </root>
将 Stan 的号码更改为 455:
xmlstarlet ed \ --var name '"Stan"' \ --var value '455' \ --update '//entry[name = $name]/number' \ --expr '$value' file.xml
这与第一个命令类似,它使用包含 XPath 字符串的
entry
内部变量来选择我们感兴趣的节点。$name
它不会删除找到的节点,而是number
使用内部变量中提供的值更新子节点$value
。生成的文档:
<?xml version="1.0"?> <root> <entry> <name>Joe</name> <number>133</number> </entry> <entry> <name>Mary</name> <number>123</number> </entry> <entry> <name>Stan</name> <number>455</number> </entry> </root>
清空斯坦的记录:
xmlstarlet ed \ --var name '"Stan"' \ --update '//entry[name = $name]' \ --value '' file.xml
这再次表明我们可以通过将节点的值更新为空字符串来清空节点。
生成的文档:
<?xml version="1.0"?> <root> <entry> <name>Joe</name> <number>133</number> </entry> <entry> <name>Mary</name> <number>123</number> </entry> <entry/> </root>
包装xq
器jq
解析 XML 文档并将其转码为 JSON。然后,它将jq
表达式应用于生成的 JSON 文档,并可选择将其转换回 XML。
鉴于本答案开头的文档,即使输入是 XML 文档,xq
也会在内部使用如下所示的 JSON 文档:
{
"root": {
"entry": [
{
"name": "Joe",
"number": "133"
},
{
"name": "Mary",
"number": "123"
},
{
"name": "Stan",
"number": "233"
}
]
}
}
删除斯坦:
xq --xml-output \ --arg name 'Stan' \ 'del(.root.entry[] | select(.name == $name))' file.xml
这使用 的
del()
功能jq
来删除给定的路径。通过从数组中选择元素来找到路径,该.root.entry
数组.name
的键的值为$name
,我们在命令行上设置的内部变量。将 Stan 的号码更改为 455:
xq --xml-output \ --arg name 'Stan' \ --arg value 455 \ '(.root.entry[] | select(.name == $name)).number |= $value' file.xml
这与前面的表达式类似,但我们不是使用 删除选定的节点,而是使用内部变量
del()
访问键并更新其值。.number
$value
清空斯坦的记录:
xq --xml-output \ --arg name 'Stan' \ '(.root.entry[] | select(.name == $name)) |= null' file.xml
同样,我们使用类似的表达式来选择我们感兴趣的节点,然后更新它以
null
将其清空。使用empty
in 代替null
会删除节点,因此这是实现与上面第一点相同结果的另一种方法。
xmlstarlet
这些和xq
/表达式之间的主要区别jq
在于,我们使用的是绝对路径 with xq
,而我们//
在 XPath 表达式中使用 withxmlstarlet
来递归搜索我们感兴趣的节点。您也可以使用递归搜索xq
,但这有点棘手,我们选择在这里使用的示例不需要它。