如何删除文本文件中两个关键字之间的文本

如何删除文本文件中两个关键字之间的文本

上下文:GNU/Linux Ubuntu。

我有一个由数千行组成的文件,我想要一个脚本来删除两个特定关键字之间的一些行。
初始文件如下:

bla bla
...
bla bla
keyword1
bla bla
...
bla bla
keyword2
bla bla
...
bla bla

我想保留除keyword1和之间的部分之外的所有文件keyword2

让我们考虑一下,keyword1keyword2在文件中只出现一次;这些关键字的行前或后可能有其他字符,例如空格或<>

带有关键字的行看起来像这样(它们实际上是基于 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)中,unlessif 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>

任务也有点不清楚,所以我将展示如何

  1. entry删除给定值的节点之一name
  2. 给定一个值,更改number一个节点的值。entryname
  3. entry给定一个值,删除其中一个节点的内容name

首先使用相当常见的命令行 XML 解析器来完成此操作xmlstarlet,然后使用鲜为人知的xq(来自https://kislyuk.github.io/yq/),著名的 JSON 解析器的包装器jq

首先,使用 XPath 语法xmlstarlet

  1. 删除斯坦:

    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>
    
  2. 将 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>
    
  3. 清空斯坦的记录:

    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>
    

包装xqjq解析 XML 文档并将其转码为 JSON。然后,它将jq表达式应用于生成的 JSON 文档,并可选择将其转换回 XML。

鉴于本答案开头的文档,即使输入是 XML 文档,xq也会在内部使用如下所示的 JSON 文档:

{
  "root": {
    "entry": [
      {
        "name": "Joe",
        "number": "133"
      },
      {
        "name": "Mary",
        "number": "123"
      },
      {
        "name": "Stan",
        "number": "233"
      }
    ]
  }
}
  1. 删除斯坦:

    xq --xml-output \
        --arg name 'Stan' \
        'del(.root.entry[] | select(.name == $name))' file.xml
    

    这使用 的del()功能jq来删除给定的路径。通过从数组中选择元素来找到路径,该.root.entry数组.name的键的值为$name,我们在命令行上设置的内部变量。

  2. 将 Stan 的号码更改为 455:

    xq --xml-output \
        --arg name 'Stan' \
        --arg value 455 \
        '(.root.entry[] | select(.name == $name)).number |= $value' file.xml
    

    这与前面的表达式类似,但我们不是使用 删除选定的节点,而是使用内部变量del()访问键并更新其值。.number$value

  3. 清空斯坦的记录:

    xq --xml-output \
        --arg name 'Stan' \
        '(.root.entry[] | select(.name == $name)) |= null' file.xml
    

    同样,我们使用类似的表达式来选择我们感兴趣的节点,然后更新它以null将其清空。使用emptyin 代替null会删除节点,因此这是实现与上面第一点相同结果的另一种方法。

xmlstarlet这些和xq/表达式之间的主要区别jq在于,我们使用的是绝对路径 with xq,而我们//在 XPath 表达式中使用 withxmlstarlet来递归搜索我们感兴趣的节点。您也可以使用递归搜索xq,但这有点棘手,我们选择在这里使用的示例不需要它。

相关内容