正则表达式匹配和子字符串提取

正则表达式匹配和子字符串提取

我有这些文件,由许多不同的人手动创建。格式虽然遵循一定的规则,但并不统一。

想想下面这三行

"erroneous_data_F08R16_recordeded_by_tech21"
"erroneous_data_F8R16_recordeded_by_tech021"
"erroneous_data_F008R016_recordeded_by_tech21"

它们都指向同一件事 F008 或 F08 或 F8 表示文件号 8 R16 或 R016 或(R[单位数] 如果可能)表示行号 16

给定文件中有任意数量的这些行,将使用while read line循环进行扫描。

我想做的是使文件和行号部分统一,例如上面三行示例的F008R016,因为我的文件号不超过3位(达到999后滚动,行数永远不会)每个文件中都有多个,但为了保持一致性,假设它是 3 位数字,在这个文件中我需要处理,还存在非结构化注释,因此首要任务是检测行并将它们分成几行。不同的临时文件,然后使它们统一。

为了实现这一目标,我的计划是回显该行并 grep 匹配模式的正则表达式。不幸的是,正则表达式不是我的强项。

到目前为止,我一直停留在检测行中的 file#row# 结构上

cat InputFile | while read line
do
  echo $line | grep '[F,f]\d\d[R,r]\d\d' >/dev/null  #this is assuming two digit file number and 2 digit row number 
  result=$?
  if [ $result -eq 0 ]
  then
    echo $line >tempfile
  fi
done

grep 命令上的此正则表达式匹配始终失败,即使该行包含 F08R16 模式也是如此。

完成此操作后,我想将此子字符串提取到变量中并分析变量的结构,并在必要时添加前导零以使其统一。

非常感谢任何纠正我的正则表达式并实现提取变量的更高目标的建议。

无论如何,我当时正在开发 CentOS 6.7 版,但我还有其他发行版可供使用。

答案1

我假设您想要匹配 anf或 an F,然后匹配 1、2 或 3 个数字,然后是 or rR然后再次匹配 1、2 或 3 个数字,直到 a _。如果是这样,你可以这样做(使用 GNU grep):

grep -iP 'f\d{1,3}r\d{1,3}_' InputFile > tmpfile

或者,对于非 GNU grep

grep -iE 'f[0-9]{1,3}r[0-9]{1,3}_' InputFile > tmpfile

然而,这几乎可以肯定是一个XY问题。你真的不想在 shell 中做这种事情。例如,这一perl行将正确格式化所有相关行:

$ perl -pe 's/_f(\d+)r(\d+)_/sprintf("_F%03dR%03d_",$1,$2)/ei' file
"erroneous_data_F008R016_recordeded_by_tech21"
"erroneous_data_F008R016_recordeded_by_tech021"
"erroneous_data_F008R016_recordeded_by_tech21"

这只是为了让您了解可以使用哪些技巧来避免此类问题。

答案2

echo这样grep——那太疯狂了。

<infile grep -iE '([fr][0-9]+){2}' >outfile

...应该让你得到你所询问的台词。调用cat通过管道将文件写入 shell,然后read逐字节解释并消除各种 shell 语法字符,然后逐字节将其复制到另一个管道,echo以便您可以默默地grep成功成功......好吧...

grep只会将匹配结果写给您。如果您想要匹配行的计数或其他内容,请使用-c.如果您想要匹配行的行号,请使用-n.如果您想要不区分大小写的匹配,请使用-i.也许尝试man grep更多。

要实时编辑流,您可以使用sed

sed -Ee:t -e's/((_)[Ff]|[0-9]{3,}[Rr])([0-9]{1,2}(\2|[Rr]))/\10\3/g;tt'

你需要一个 GNU/BSD/ASTsed才能工作。但它的效果非常好:

sed -Ee:t -e's/((_)[Ff]|[0-9]{3,}[Rr])([0-9]{1,2}(\2|[Rr]))/\10\3/g;tt' \
<<""
"erroneous_data_F08R16_recordeded_by_tech21"
"erroneous_data_F8R16_recordeded_by_tech021"
"erroneous_data_F008R016_recordeded_by_tech21"

"erroneous_data_F008R016_recordeded_by_tech21"
"erroneous_data_F008R016_recordeded_by_tech021"
"erroneous_data_F008R016_recordeded_by_tech21"

你也不是第一个来这里抱怨 tech 21 的人。应该有人纠正那个家伙。

答案3

terdon的perl答案当然很优雅,我同意:如果目标是使所有数据的格式统一/一致,则无需单独分离出需要更改的行。如果您不喜欢perl (或者万一您没有),这里有一个sed解决方案:

sed -re 's/_[Ff]([0-9]+)[Rr]([0-9]+)_/_F00\1R00\2_/' \
                                          -e 's/_F0*([0-9]{3})R0*([0-9]{3})_/_F\1R\2_/'

可以将其键入为一行(省略\第一行末尾的 )。我承认,这并不像perl解决方案那么优雅。它分两步进行:

  • 在模式中的每个or (或or )  00之后添加  。这会将个位数更改为,将两位数更改为,将三位数更改为 。 (第一步也大写or 。)FRfr_ F file_number R file_number _800808000800800008
    fr
  • 在模式中的  每个F或之后  ,删除最后三位数字之前出现的任意多个零。所以被保留,而和被更改为 。R_ F file_number R file_number _008000800008008

如果您的版本sed不支持-r(使用扩展正则表达式)选项,请使用

sed -e 's/_[Ff]\([0-9]*\)[Rr]\([0-9]*\)_/_F00\1R00\2_/' \
                                          -e 's/_F0*\([0-9]{3}\)R0*\([0-9]{3}\)_/_F\1R\2_/'

使用\(…\)代替(…)*代替+。 (*+并不意味着同一件事,但在这种情况下它们足够接近,除非有带有像_FR42_or 之类的字符串的行_F17R_。事实上,您也可以在第一个命令中使用*代替。)+

如何使用这些

  • sed option(s) scripts InputFile
    或 处理输入文件并在屏幕上查看结果。
    sed option(s) scripts < InputFile
  • sed option(s) scripts InputFile > output_file
    或 处理输入文件并将结果发送到新文件。
    sed option(s) scripts < InputFile > output_file
  • sed -i option(s) scripts InputFile
    处理文件并就地修改它;即,将结果发送回原始文件。

相关内容