![使用 AWK 提取以 *** 分隔的段落](https://linux22.com/image/66712/%E4%BD%BF%E7%94%A8%20AWK%20%E6%8F%90%E5%8F%96%E4%BB%A5%20***%20%E5%88%86%E9%9A%94%E7%9A%84%E6%AE%B5%E8%90%BD.png)
我有一个如下所示的文件:
blablabla
blablabla
***
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
blablabla
blablabla
我想提取带有 的段落thingsIwantToRead
。当我不得不处理这样的问题时,我使用了AWK像这样:
awk 'BEGIN{ FS="Separator above the paragraph"; RS="" } {print $2}' $file.txt | awk 'BEGIN{ FS="separator below the paragraph"; RS="" } {print $1}'
它起作用了。
在这种情况下,我尝试输入FS="***"
, "\*{3}"
, "\*\*"
(它不起作用,因为 AWK 将其视为普通星号),"\\*\\*"
或我能想到的任何正则表达式,但它不起作用(它不打印任何内容)。
你知道为什么吗?
如果没有,您知道解决我的问题的另一种方法吗?
下面是我要解析的文件的摘录:
13.2000000000 , 3*0.00000000000 , 11.6500000000 , 3*0.00000000000 , 17.8800000000
Blablabla
SATELLITE EPHEMERIS
===================
Output frame: Mean of J2000
Epoch A E I RA AofP TA Flight Ang
*****************************************************************************************************************
2012/10/01 00:00:00.000 6998.239 0.001233 97.95558 77.41733 89.98551 290.75808 359.93398
2012/10/01 00:05:00.000 6993.163 0.001168 97.95869 77.41920 124.72698 274.57362 359.93327
2012/10/01 00:10:00.000 6987.347 0.001004 97.96219 77.42327 170.94020 246.92395 359.94706
2012/10/01 00:15:00.000 6983.173 0.000893 97.96468 77.42930 224.76158 211.67042 359.97311
<np>
----------------
Predicted Orbit:
----------------
Blablabla
我想提取:
2012/10/01 00:00:00.000 6998.239 0.001233 97.95558 77.41733 89.98551 290.75808 359.93398
2012/10/01 00:05:00.000 6993.163 0.001168 97.95869 77.41920 124.72698 274.57362 359.93327
2012/10/01 00:10:00.000 6987.347 0.001004 97.96219 77.42327 170.94020 246.92395 359.94706
2012/10/01 00:15:00.000 6983.173 0.000893 97.96468 77.42930 224.76158 211.67042 359.97311
我尝试用来获取 * 行后的数字的命令:
`awk 'BEGIN{ FS="\\*{2,}"; RS="" } {print $2}' file | awk 'BEGIN{ FS="<np>"; RS="" } {print $1}'`
答案1
告诉 awk 在两个分隔符之间进行打印。具体来说:
awk '/\*{4,}/,/<np>/' file
这还将打印包含分隔符的行,因此您可以使用以下命令删除它们:
awk '/\*{4,}/,/<np>/' file | tail -n +2 | head -n -1
或者,如果某行与第一个分隔符匹配,则可以将变量设置为 true;如果与第二个分隔符匹配,则可以将变量设置为 false,并且仅在为 true 时才打印:
awk '/\*{4,}/{a=1; next}/<np>/{a=0}(a==1){print}' file
a
如果当前行匹配 4 个或更多,上面的命令将设置为 1 *
,并且也会跳到该next
行。这意味着该***
行永远不会被打印。
这是对问题最初的、被误解的版本的回答。我将其留在这里,因为它在稍微不同的情况下可能很有用。
首先,您不需要FS
(字段分隔符),您想要RS
(记录分隔符)。然后,要传递文字*
,您需要将其转义两次。一次转义 the *
,一次转义反斜杠(否则,awk 将尝试以与\r
or相同的方式匹配它\t
)。然后,打印第二“行”:
$ awk -vRS='\\*\\*\\*' 'NR==2' file
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
为了避免输出周围出现空行,请使用:
$ awk -vRS='\n\\*\\*\\*\n' 'NR==2' file
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
请注意,这假设***
之后每个段落,不仅仅是在您展示的第一个段落之后。
答案2
除了@terdon的答案之外,使用 awk (和 sed),您可以使用范围模式:
awk '/sep1/,/sep2/{print}' file
或者
sed -n '/sep1/,/sep2/p' file
将打印所有内容(包括)sep1
和sep2
.那是:
~$ awk '/sep1/,/sep2/{print}' file
sep1
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
sep2
在你的情况下:
~$ awk '/\*\*\*/,/^$/{print}' file
***
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
然后您可能想删除第一行和最后一行。
例如:
~$ sed -n '/\*\*\*/,/^$/p' file | sed '1d;$d'
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
或者
~$ awk '/\*\*\*/,/^$/{print}' file | awk 'NR>1&&!/^$/ {print}'
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
如果你的段落不太长。
答案3
有sed
两种方法可以解决这个问题。您可以选择包容性的或者只。在你的情况下,包括的选择意味着打印以匹配项开头的所有行,'^*\*\*'
最多包含其中之一^ *<np>
(无论那是什么)或^$
空行。
一个包括的可以使用其他答案中演示的任何范围表达式来指定选择,并涉及指定在这里开始打印模式通过到一路经过这里图案。
一个独家的选择以相反的方式进行。它指定了一个在此之前停止打印模式通过到从这里开始打印图案。对于您的示例数据 - 并允许在此之前停止打印将匹配任一空白行的模式或者那个<np>
东西:
sed -e 'x;/^\( *<np>.*\)*$/,/^*\** *$/c\' -e '' <infile >outfile
x
- 交换保持空间和模式空间。该机构设立了一个向后看-
sed
始终在输入后面一行 - 并且第一行始终为空白。
- 交换保持空间和模式空间。该机构设立了一个向后看-
/^\( *<np>.*\)*$/
- 这选择了一个在此之前停止打印匹配组中从头到尾匹配零次或多次的行。有两种行可以匹配零次或多次出现的行 - 空行或具有任意数量的空行<空格>位于行首,后跟字符串
<np>
。
- 这选择了一个在此之前停止打印匹配组中从头到尾匹配零次或多次的行。有两种行可以匹配零次或多次出现的行 - 空行或具有任意数量的空行<空格>位于行首,后跟字符串
/^*\** *$/
- 这选择了一个从这里开始打印至少以一个
*
星号字符开始的行,并继续到该行的末尾,仅出现零次或多次*
星号,并且可能以任意数量的空格结束。
- 这选择了一个从这里开始打印至少以一个
c\' -e ''
- 这
c
会将整个被阻止的选择挂到一个空行,将所有不需要的行压缩到字符串中EOF
。
- 这
^*\** *$
因此,在第一个后续段落之前和之后出现的任意数量的行^\( *<np>.*\)*$
总是被压缩为只有一个空白,并且只有第一个出现的段落后的匹配项^*\** *$
将打印到标准输出。它打印...
2012/10/01 00:00:00.000 6998.239 0.001233 97.95558 77.41733 89.98551 290.75808 359.93398 2012/10/01 00:05:00.000 6993.163 0.001168 97.95869 77.41920 124.72698 274.57362 359.93327 2012/10/01 00:10:00.000 6987.347 0.001004 97.96219 77.42327 170.94020 246.92395 359.94706 2012/10/01 00:15:00.000 6983.173 0.000893 97.96468 77.42930 224.76158 211.67042 359.97311
假设您想要处理输入中出现任意数量的段落模式。如果你只想要第一的然而,只要你有 GNUgrep
并且infile
是一个常规的,可搜索的文件:
{ grep -xm1 '*\** *' >&2
sed -n '/^\( *<np>.*\)*$/q;p'
} <infile 2>/dev/null >outfile
...也会起作用。
事实上,我想,有三方法。第三个可能看起来像:
sed 'H;$!d;x;s/\(\n\*\** *\n\(\([0-9./: ]*\n\)*\)\)*./\2/g'
...它读取整个文件,然后全局替换掉不属于匹配行规范的每个字符。它的打印效果和以前一样,但是写起来很痛苦,而且只有当你平衡可选选项时,它们才是安全的性能。任何特点。
答案4
根据问题的编辑更新版本:
使用 Perl:
< inputfile perl -0777 -pe 's/.*[*]+\n(.*) <np>\n.*/$1/s' > outputfile
< inputfile
: 将内容重定向inputfile
到perl
'sstdin
-0777
:强制 Perl 一次性读取整个文件,而不是逐行读取-p
: 强制 Perl 打印这些行-e
:强制 Perl 从参数中读取一行程序> outputfile
perl
:将 的内容重定向stdout
到outputfile
正则表达式分解:
s
:断言执行替换/
:开始搜索模式.*[*]+\n
:匹配任意数量的任意字符,直到以一个或多个字符结尾且*
紧跟换行符的字符串末尾(.*) <np>
:匹配并分组任意数量的任意字符,最多可达紧随其后的字符串的任意<np>\n
字符.*
: 匹配任意数量的任意字符/
:停止搜索模式/开始替换模式$1
: 替换为捕获的组/
:停止替换模式/启动修饰符s
:断言将输入字符串视为单行,强制.
也匹配换行符
示例输出:
~/tmp$ cat inputfile
13.2000000000 , 3*0.00000000000 , 11.6500000000 , 3*0.00000000000 , 17.8800000000
Blablabla
SATELLITE EPHEMERIS
===================
Output frame: Mean of J2000
Epoch A E I RA AofP TA Flight Ang
*****************************************************************************************************************
2012/10/01 00:00:00.000 6998.239 0.001233 97.95558 77.41733 89.98551 290.75808 359.93398
2012/10/01 00:05:00.000 6993.163 0.001168 97.95869 77.41920 124.72698 274.57362 359.93327
2012/10/01 00:10:00.000 6987.347 0.001004 97.96219 77.42327 170.94020 246.92395 359.94706
2012/10/01 00:15:00.000 6983.173 0.000893 97.96468 77.42930 224.76158 211.67042 359.97311
<np>
----------------
Predicted Orbit:
----------------
Blablabla
~/tmp$ < inputfile perl -0777 -pe 's/.*[*]+\n(.*) <np>\n.*/$1/s'
2012/10/01 00:00:00.000 6998.239 0.001233 97.95558 77.41733 89.98551 290.75808 359.93398
2012/10/01 00:05:00.000 6993.163 0.001168 97.95869 77.41920 124.72698 274.57362 359.93327
2012/10/01 00:10:00.000 6987.347 0.001004 97.96219 77.42327 170.94020 246.92395 359.94706
2012/10/01 00:15:00.000 6983.173 0.000893 97.96468 77.42930 224.76158 211.67042 359.97311
~/tmp$
原始版本:
使用 Perl:
< inputfile perl -0777 -pe 's/.*[*]{3}\n(.*\n)\n.*/$1/s' > outputfile
< inputfile
: 将内容重定向inputfile
到perl
'sstdin
-0777
:强制 Perl 一次性读取整个文件,而不是逐行读取-p
: 强制 Perl 打印这些行-e
:强制 Perl 从参数中读取一行程序> outputfile
perl
:将 的内容重定向stdout
到outputfile
正则表达式分解:
s
:断言执行替换/
:开始搜索模式.*[*]{3}\n
***\n
:匹配字符串末尾之前的任意数量的任意字符(.*\n)\n
:匹配并分组任意数量的任意字符,最多为换行符,后跟换行符.*
: 匹配任意数量的任意字符/
:停止搜索模式/开始替换模式$1
: 替换为捕获的组/
:停止替换模式/启动修饰符s
:断言将输入字符串视为单行,强制.
也匹配换行符
示例输出:
~/tmp$ cat inputfile
blablabla
blablabla
***
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
blablabla
blablabla
~/tmp$ < inputfile perl -0777 -pe 's/.*[*]{3}\n(.*\n)\n.*/$1/s'
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
~/tmp$