如果特定行包含子字符串则输出其他子字符串

如果特定行包含子字符串则输出其他子字符串

我想在文件的每一行上查找一个字符串,如果存在,则返回不同的特定字符串。

解决方案发布后,该帖子已被编辑,以帮助更好地陈述问题(因此一些早期的回应不再适用)

我有这个代码:

Numlines=$(grep "" -c File.txt)
for (( line=1; line<=$Numlines; line++ )) ; do 
awk -v line="$line" 'NR==line ...???

我正在寻找的字符串是style-name="T。如果该字符串在 for 循环中的一行中,则返回紧跟在 之后的数字T。 中的行File.txt可能包含类似 的字符串style-name="T2",在这种情况下,我只想返回2。 字符串在 中的每一行中并不位于相同的位置File.txt,因此我认为我不能在 中使用字段指定awk

如果我没记错的话,"/style-name\=\"T/"应该提供匹配项,但如果我在代码中使用它,它要么会出错,要么什么都不返回。也许脚本会检查它是否可以产生匹配项,如果可以,则使用第二行代码来获取字符串,尽管我认为awk一旦找出前体代码,就可以用一行代码完成它。

以下是一个示例File.txt

<TEST1>     <text:p text:style-name="P4">Hello<text:span text:style-name="T2">world</text:span></text:p>
<tyi.ggg>     <text:p text:style-name="P9">Hi<text:span text:style-name="T16">there</text:span></text:p>
<TEST2>     <text:p text:style-name="P540">0 <text:s/>oooh yeah<text:s text:c="2"/>kool-aid<text:s text:c="12"/>0:00</text:p>

第一行(第一次循环)的输出for应为 2。第二行(第二次循环)的输出for应为 16。第三行的输出应为零。

答案1

我使用下面的文本作为示例(修改了OP提供的示例):

<TEST1>     <text:p text:style-name="P4">Hello<text:span text:style-name="T2">world</text:span><text:span text:style-name="T3"></text:p>
<TEST2>     <text:p text:style-name="P540">0 <text:s/>oooh yeah<text:s text:c="2"/>kool-aid<text:s text:c="12"/>0:00</text:p>
<ANOTHER_TEST15>     <text:p text:style-name="P9">Hi<text:span text:style-name="T16">there</text:span></text:p>

我想出了这个grepsed命令的组合,它可能不是最有效的,但相对容易理解:

grep -n 'style-name="T' File.txt | grep -P -o '^\d+:<\w*>|style-name="T\d+' | sed -z 's/style-name="//g; s/:/ /; s/\nT/ T/g'

分解命令:

  • grep -n 'style-name="T' File.txtstyle-name="T添加匹配的行号。

    输出为:

    1:<TEST1>     <text:p text:style-name="P4">Hello<text:span text:style-name="T2">world</text:span><text:span text:style-name="T3"></text:p>
    3:<ANOTHER_TEST15>     <text:p text:style-name="P9">Hi<text:span text:style-name="T16">there</text:span></text:p>
    
  • grep -P -o '^\d+:<\w*>|style-name="T\d+':<使用先前的输出作为输入,并将行号与行号内部和>旁边的文本以及style-name="T行号旁边的数字进行匹配。每个匹配都打印在新行中。

    输出为:

    1:<TEST1>
    style-name="T2
    style-name="T3
    3:<ANOTHER_TEST15>
    style-name="T16
    
  • sed -z 's/style-name="//g; s/:/ /; s/\nT/ T/g'使用先前的输出作为输入并删除style-name="和并将( ):之前的换行符替换为后跟( ) 的单个空格。T\nTTT

    输出为:

    1 <TEST1> T2 T3
    3 <ANOTHER_TEST15> T16
    

答案2

我认为使用 shell 循环多次处理文件没有任何价值。

在 GNU awk 中,你可以使用函数将子模式捕获到数组中match。因此,你可以简单地执行以下操作:

gawk 'match($0,/style-name="T([0-9]+)"/,m){print m[1]}' File.txt

在常规 POSIX awk 中,该match函数没有此扩展功能,但您可以使用它RSTARTRLENGTH提取所需的子字符串:

awk 'match($0,/style-name="T[0-9]+"/){print substr($0,RSTART+13,RLENGTH-14)}' File.txt

[如果你必须在 shell 循环中执行此操作,每次迭代时出于某些未指明的原因针对特定的行号,然后您可以将规则更改为NR==line && match(...) {...}]。


如果需要提取多种的 T#每条记录的值,你可以将其包装match在一个循环中,例如$0

gawk '
  {test=$1}       # save the `TEST#` before we start the `match` loop 
  {
    T=""
    while(match($0,/style-name="T([0-9]+)"/,m)) {
      T = T=="" ? m[1] : T OFS m[1]     # append the `T#`
      $0 = substr($0,RSTART+RLENGTH)    # remove the part we already matched
    }
  } 
  T !="" {
    print NR,test,T    # print the record (line) number, `TEST#`, and accumulated `T#`s
  }
' File.txt
1 <TEST1> 2
2 <TEST2> 16

要在非 GNU awk 中使用我之前展示的RSTARTand方法执行相同的操作,请将循环替换为:RLENGTHwhile

while(match($0,/style-name="T[0-9]+"/)) {
  t = substr($0,RSTART+13,RLENGTH-14)
  T = T=="" ? t : T OFS t
  $0 = substr($0,RSTART+RLENGTH)    # remove the part we already matched
}

相关内容