我有一个包含多行的文件,如果它们都符合特定模式,我想将它们连接起来。
我知道我可以找到适合该模式的行并使用以下命令获取下一行:
grep -E -A1 'Pattern' filename
但是我如何检查下一行是否也符合该模式以及如何将两者连接起来?
例如,我有一个这样的文件:
Hello
i
am
John
Smith
示例模式如下:
'^[A-Z][a-z]+'
因此,在这种情况下,如果行都以大写字母开头,我想合并这些行。
我想要实现的输出是:
Hello
i
am
John Smith
答案1
/^[A-Z][a-z]+/{
:a
N
/\n[A-Z][a-z]+/{
s/\n/ /
b a
}
}
将其另存为join.sed
并执行:sed -Ef join.sed file
。
如果该行与模式匹配,我们将启动一个循环,将下一行附加到模式空间,并用空格替换换行符,只要该行也与模式匹配。
对于 GNU Sed,你可以将其折叠成一行:
sed -E '/^[A-Z][a-z]+/{:a;N;/\n[A-Z][a-z]+/{s/\n/ /;b a}}' file
或者,一个 Awk 脚本join.awk
,其模式应为p
:
{
if($0~p)c+=1
else c=0
printf "%s%s", (c>1 ? " " : ors), $0
ors=ORS
}
END{print ""}
执行:awk -f join.awk p='^[A-Z][a-z]+' file
。
答案2
使用sed
空字符作为分隔符 ( -z
):
$ sed -z 's/\([A-Z][a-z]\+\)\n\([A-Z][a-z]\+\)/\1 \2/'
Hello
i
am
John Smith
答案3
使用 Raku(以前称为 Perl_6)
raku -e 'given lines.join("\n") { S/ $<first>=[<upper><lower>+] \n $<last>=[<upper><lower>+] /$<first> $<last>/.put};'
输入示例:
Hello
i
am
John
Smith
goodbye
示例输出:
Hello
i
am
John Smith
goodbye
上面是用 Raku(Perl 语言家族的成员)编写的解决方案。数据given
以 的形式发送给 Raku lines
,但由于 Raku 的lines
例行自动剪切输入,数据会join
以换行符进行 -ed。虽然这看起来有点复杂,但优点是 Raku 的lines
例程可以延迟读取数据,这意味着上面的代码应该内存效率高。
Raku 实现了一种S///
“非破坏性”运算符,它与熟悉的s///
运算符类似(如果不相同)(Raku 也有该运算符)。资本运营S
商有一个优势,那就是“保留原始字符串完整并返回结果字符串而不是 $/ (匹配变量)。”
在运算符的匹配(左)一半内S///
,命名捕获被雇用。正则表达式引擎首先搜索[<upper><lower>+]
并将其分配给命名的 capture $<first>
,然后搜索一个\n
(换行符),最后搜索另一个[<upper><lower>+]
,这次将其分配给命名的 capture $<last>
。最后,在运算符的替换(右)半部分中S///
,两个命名捕获$<first> $<last>
用于替换左侧匹配,尽管和一个空间和没有\n
之间的换行符。
完成同样事情的另一种方法如下。该代码省略了命名捕获,而是使用<(\n)>
从匹配对象中删除除捕获标记内的内容之外的所有内容<(…)>
。然后在替换中,\n
用空格替换:
raku -e 'put S/ [<upper><lower>+] <(\n)> [<upper><lower>+] / / given lines.join("\n");'
[注意,上面的代码只会将George\nHerbert\nWalker\nBush
4 行折叠成 3 行 ( George Herbert\nWalker\nBush
)。如果您希望在一行上返回所有按行连续出现的情况[<upper><lower>+]
,请随时发布该问题]。
https://docs.raku.org/language/regexes#S///_non-breaking_substitution
https://docs.raku.org/language/regexes#index-entry-regex__Named_captures-Named_captures
https://raku.org