替换行之间的模式

Replace a pattern between lines

我正在尝试替换文件行之间的模式。

具体来说,我想在大型和多个文件中将 ,\n & 替换为 , &\n 。这实际上将符号 & 移动到上一行。使用 CTR+H 这很容易,但我发现使用 sed 很难。

所以,初始文件的格式如下:

      A +,
   &  B -,
   &  C ),
   &  D +,
   &  E (,
   &  F *,
 # &  G -,
   &  H +,
   &  I (,
   &  J +,
      K ?,

输出形式为:

      A +, &
      B -, &
      C ), &
      D +, &
      E (, &
      F *, &
#  &  G -,
      H +, &
      I (, &
      J +,
      K ?,

根据之前关于 Whosebug 的回答问题,我尝试使用以下命令对其进行转换:

sed ':a;N;$!ba;s/,\n &/&\n /g' file1.txt > file2.txt

sed -i -e '$!N;/&/b1' -e 'P;D' -e:1 -e 's/\n[[:space:]]*/ /' file2.txt

但如果文件中存在符号“#”,它们就会失败。

有什么方法可以更简单的替换匹配的模式,比方说: sed -i 's/,\n &/, &\n /g' file

提前致谢!

使用sed

$ sed ':a;N;s/\n \+\(&\) \(.*\)/ \n     /;ba' input_file
      A +, &
      B -, &
      C ), &
      D +, &
      E (, &
      F *,
 # &  G -, &
      H +, &
      I (, &
      J +,

如果您使用 GNU sed 并且您的文件不包含 NUL 字符(ASCII 代码 0),您可以使用其 -z 选项将整个文件作为一个字符串处理,并且multi-line 替代命令的模式(m 标志)。 m 标志不是绝对需要的,但它简化了一点(. 和字符 类 不匹配换行符):

$ sed -Ez ':a;s/((\`|\n)[^#]*,)((\n.*#.*)*)(\n[[:blank:]]*)&/ \& /gm;ta' file
      A +, &
      B -, &
      C ), &
      D +, &
      E (, &
      F *, &
 # &  G -,
      H +, &
      I (, &
      J +,
      K ?,

这对应于您的文本说明和您展示的示例所需的输出。但这有点复杂。它不处理以换行符结尾的行,而是处理以换行符(或文件开头)开头并在下一个换行符之前结束的 sub-strings 。让我们将这些命名为“chunks”。

我们搜索形式为 AB*C 的块序列,其中:

  • A 是一个不包含 # 的块(可能是第一个)。它由 (\<backtick>|\n)[^#]*, 匹配,这意味着 beginning-of-file-or-newline,后跟除换行符和 # 之外的任意数量的字符,后跟一个逗号。
  • B* 是包含 # 的任意数量(包括 none)块。它由 \n.*#.* 匹配,这意味着换行符,后跟除换行符之外的任意数量的字符,然后是 # 和除换行符之外的任意数量的字符。
  • C 是一个以换行符开头的块,然后是 spaces 和 &。它由 \n[[:blank:]]*& 匹配,这意味着换行符,后跟任意数量的空格和 &.

如果我们找到这样一个 AB*C 序列我们在 A 的末尾添加一个 space 和一个 &,我们不改变 B* ,然后我们将 C 中的第一个 & 替换为 space。然后我们重复,直到找不到这样的序列。

注意:如果逗号后面可以跟换行符之前的空格,我们必须考虑到它们。如果你想保留它们:

$ sed -Ez ':a;s/((\`|\n)[^#]*,[[:blank:]]*)((\n.*#.*)*)(\n[[:blank:]]*)&/ \& /gm;ta' file

其他:

$ sed -Ez ':a;s/((\`|\n)[^#]*,)[[:blank:]]*((\n.*#.*)*)(\n[[:blank:]]*)&/ \& /gm;ta' file

假设行

 # &  G -,

是一个注释行,稍后可能会取消注释,处理该行中的 & 也可能有意义。不知道数据的用途,这可能有用也可能没用。

GNUawk,命令

awk 'BEGIN { RS=",";ORS="" } { printf "%s%s", ORS, gensub(/(\n[ \t#]*)&/, " \&\1 ",1); ORS=RS }' inputfile

会转入

      A +,
   &  B -,
   &  C ),
   &  D +,
   &  E (,
   &  F *,
 # &  G -,
   &  H +,
   &  I (,
   &  J +,
      K ?,

进入

      A +, &
      B -, &
      C ), &
      D +, &
      E (, &
      F *, &
 #    G -, &
      H +, &
      I (, &
      J +,
      K ?,

此脚本只有在最后一行以换行符终止或者,.

后有任何其他字符时才能正确运行

解释:

  • RS="," 将逗号设置为记录分隔符而不是输入的换行符。
  • ORS="" 将输出记录分隔符设置为第一条记录之前的空字符串。
  • fprintf "%s%s", ORS, gensub(...) pre 挂起记录分隔符而不是追加它。
  • gensub GNU 特定的替换函数,允许反向引用匹配的组。
  • /(\n[ \t#]*)&/ 搜索模式:括号定义了一个组 (1),它由一个换行符 \n 后跟任何 space 序列、制表符或注释字符 [ \t#]*.该组后跟一个 & 字符。
  • " \&\1 " 替换:space 后跟 &,后跟捕获组 (1) (\1) 和一个额外的 space 来替换已删除 &。 (\& 是获取文字 & 字符而不是插入整个匹配项所必需的。)
  • ORS=RS 将输出记录分隔符设置为第一行后的 ,。 (事实上​​,在每个 ros 之后)在第二个和后续记录之前添加一个逗号。这确保了应该是换行符的最后一条记录不会得到尾随 ,.

GNU Awk 脚本版本低于 如果输入文件的最后一行 not 以换行符终止,则将按预期 only 工作。 它将创建一个带有 , 的附加行,因为包含换行符的最后一条记录将由输出记录分隔符 ,.

终止
awk 'BEGIN { RS=ORS="," } { print gensub(/(\n[ \t#]*)&/, " \&\1 ",1) }' inputfile

如果输入文件以换行符结尾,输出将是

...
      I (, &
      J +,
      K ?,
,

在最后一个 , 之后没有换行符。

使用 sed

sed -En 'H;${g;s/^\n//;s/((\n *#.*)*)\n +&(.*)/ \&\n    /gmp}' file

说明

  • -E 启用扩展正则表达式
  • -n 阻止 sed 的默认打印
  • H追加保留space
  • ${结束时
  • g 将保留 space 中的内容覆盖为模式 space
  • s/^\n//; 从保留中删除前导换行符 space
  • s/开始替补
  • ((\n *#.*)*) 捕获组 1,可选择重复匹配换行符和 # 后跟行的其余部分
  • \n +&(.*) 匹配换行符和 1+ spaces,然后匹配 & 并捕获组 3
  • 中的其余行
  • / 在此之后替换为
  • \&\n 包含捕获组和转义 &
  • 的替换模式
  • / 结束替换
  • gmp g局部替换所有出现,m多行,p打印有替换的行

输出

      A +, &
      B -, &
      C ), &
      D +, &
      E (, &
      F *, &
 # &  G -,
      H +, &
      I (, &
      J +,
      K ?,%

看到一个bash demo.

这可能适合您 (GNU sed):

sed -E '/,$/{:a;N;/#[^\n]*$/ba
        s/,((\n.*)*)\n(\s*)&/, \&\n /;h;s/(.*)\n.*//p;g;s/.*\n(.*\n)//;D}' file

形成两行 window(但如有必要,也包括注释)。

格式化第一行并打印它(如果找到则带有注释)。

删除除最后两行以外的所有行。

删除左边两行的第一行并重复。