将不同的正则表达式可能性合并为一个

join different regex possibilities into one

是否可以将以下正则表达式合二为一?

cat file.txt | \ 
sed 's/\tNULL\t/\t\N\t/g' | \
sed 's/^NULL\t/\N\t/g' | \
sed 's/\tNULL$/\t\N/g' | \
sed 's/^NULL$/\N/g'' 

也许还有一件事要补充,这每月要处理数十亿行,因此性能是一个考虑因素。

解决方案基准测试 谢谢大家的建议,perl 运行 对我来说是最快的。如果您想知道:

[/tmp]$ time cat /tmp/result_w_null.txt > /dev/null
real    0m0.045s
user    0m0.000s
sys     0m0.042s
[/tmp]$ time cat /tmp/result_w_null.txt | sed 's/\<NULL\>/\N/g' > /dev/null
real    0m5.843s
user    0m2.472s
sys     0m3.852s
[/tmp]$ time cat /tmp/result_w_null.txt | sed 's/\tNULL\t/\t\N\t/g' | sed 's/^NULL\t/\N\t/g' | sed 's/\tNULL$/\t\N/g' | sed 's/^NULL$/\N/g' > /dev/null
real    0m7.078s
user    0m7.148s
sys     0m4.963s

#Suggestions:
[/tmp]$ time cat /tmp/result_w_null.txt | awk -F'\t' -v OFS='\t' '{for (i=1;i<=NF;i++) if ($i=="NULL") $i="\N"}1' > /dev/null
real    0m20.196s
user    0m14.876s
sys     0m7.145s
[/tmp]$ time cat /tmp/result_w_null.txt | awk -v RS='(^|[\t\n])NULL(\tNULL)*([\t\n]|$)' '{ gsub(/NULL/, "\N", RT); ORS=RT} 1' > /dev/null
real    0m10.611s
user    0m8.743s
sys     0m3.754s
[/tmp]$ time cat /tmp/result_w_null.txt | sed -E ':a; s/(\t|^)NULL(\t|$)/\N/g; ta' > /dev/null
real    0m9.673s
user    0m5.723s
sys     0m5.678s
[/tmp]$ time cat /tmp/result_w_null.txt | perl -pe 's/(?:\t|^)\KNULL(?=\t|$)/\N/g' > /dev/null
real    0m4.452s
user    0m3.237s
sys     0m2.288s

您可以使用

sed -E 's/(\t|^)NULL(\t|$)/\N/g;'

如果可以连续匹配

sed -E ':a; s/(\t|^)NULL(\t|$)/\N/g; ta'

看到一个online demo

POSIX ERE 正则表达式匹配

  • (\t|^) - 捕获第 1 组(替换模式中的 </code>):制表符或字符串开头 </li> <li><code>NULL - 文字字符串
  • (\t|$) - 捕获第 2 组(替换模式中的 </code>):制表符或字符串结尾。</li> </ul> <p>对于连续匹配,您需要循环匹配,方法是设置一个标签(<code>:a),然后使用ta分支到它。这是一种解决缺少前瞻性支持的方法,该支持允许在不 消耗 的情况下检查尾随选项卡。在 Perl 中,您将使用

    perl -pe 's/(?:\t|^)\KNULL(?=\t|$)/\N/g'
    

    哪里

    • (?:\t|^) - 匹配制表符或字符串开头的非捕获组
    • \K - 丢弃到目前为止匹配的所有文本的匹配重置运算符
    • NULL - 文字字符串
    • (?=\t|$) - 正前瞻要求紧靠当前位置右侧的制表符或字符串结尾。

awk 这种用法可能更容易理解:

awk '
  BEGIN {FS = OFS = "\t"}
  {
    for (i=1; i<=NF; i++)
      if ($i == "NULL")
        $i = "\N"
    print
  }
' file.txt

或者,单行化

awk -F'\t' -v OFS='\t' '{for (i=1;i<=NF;i++) if ($i=="NULL") $i="\N"}1' file.txt

这是一个替代的 gnu-awk 解决方案:

cat file

abc   NULL  foo
NULL  bar
xyz   NULL
pqr   mnop
NULL

gnu-awk 与自定义 RS 结合使用:

awk -v RS='(^|[\t\n])NULL(\tNULL)*([\t\n]|$)' '{
gsub(/NULL/, "\N", RT); ORS=RT} 1' file

abc   \N    foo
\N    bar
xyz   \N
pqr   mnop
\N