我想在文件的每一行上查找一个字符串,如果存在,则返回不同的特定字符串。
解决方案发布后,该帖子已被编辑,以帮助更好地陈述问题(因此一些早期的回应不再适用)
我有这个代码:
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>
我想出了这个grep
和sed
命令的组合,它可能不是最有效的,但相对容易理解:
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.txt
style-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
\nT
T
T
输出为:
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
函数没有此扩展功能,但您可以使用它RSTART
来RLENGTH
提取所需的子字符串:
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 中使用我之前展示的RSTART
and方法执行相同的操作,请将循环替换为:RLENGTH
while
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
}