sed的'N'命令间歇性地工作

时间:2014-03-31 19:25:38

标签: linux bash sed

以下是我要格式化的示例文本块:

<tr><td></td><td>tear a cat in, to make all split.</td><td></td></tr>
<tr><td></td><td class="tdci">The raging rocks</td><td></td></tr>
<tr><td></td><td class="tdci">The foolish Fates.</td></tr>
<tr><td></td><td>This was lofty! Now name the rest of the players.</td><td></td></tr>

使用这两个&#39; sed&#39;脚本中的命令:

sed -ri '/^<tr><td><\/td><td>/N;s/(\n<tr><td><\/td><td class="tdci">)/\n<tr><td>\&nbsp;<\/td><\/tr>\1/' "$f"   #insert table row with empty data fields (blank line) above first line with 'class="tdci"'
sed -ri '/^<tr><td><\/td><td class="tdci">/N;s/(\n<tr><td><\/td><td>)/\n<tr><td>\&nbsp;<\/td><\/tr>\1/' "$f"   #insert table row with empty data fields (blank line) after last line with 'class="tdci"'

结果如下:

<tr><td></td><td>tear a cat in, to make all split.</td><td></td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td></td><td class="tdci">The raging rocks</td><td></td></tr>
<tr><td></td><td class="tdci">The foolish Fates.</td></tr>
<tr><td></td><td>This was lofty! Now name the rest of the players.</td><td></td></tr>

所以第一个sed命令的工作原理是在class="tdci"的第一行上面插入一个空白表行,但几乎相同的第二个sed命令意味着在第一行之后插入一个空行表行。 <{1}}的最后一行不起作用。

我通常保存这些类型的编辑,在多行之间进行编辑,因为我从来没有遇到类似命令的问题,但出于某种原因class="tdci"&#34; s&#34;对于我来说sed一直都很受欢迎,就像在这个例子中,一个实例工作正常,而第二个实例没有。该脚本在这些命令运行之前删除所有前导/尾随空格和任何Winblowz回车符(N;s/)。

由于我要编辑大量文件,我当然更愿意让这个文件在脚本中工作,如果有人能够看到任何明显的我做错了。

其他详细信息:

抱歉,我忘了提到我在Linux中运行\r(Debian stable)

2 个答案:

答案 0 :(得分:5)

从小处开始!对于您正在做的事情,这是一个更简单的测试案例:

a1
b1
b2
a2

以下是为此测试用例翻译的代码,尝试在第一个“b”之前插入c1,在最后一个之后插入c2

sed -ri '/a/N; s/(\nb)/\nc1\1/' file
sed -ri '/b/N; s/(\na)/\nc2\1/' file

第一个命令,就像你说的那样,似乎有效:

a1
c1
b1
b2
a1

第二个没有,只是给你与上面相同的结果,而不是插入c2

以下是您可能认为会发生的事情,粗体部分不正确:

  1. a1被阅读并打印。
  2. c1被阅读并打印。
  3. b1已被阅读。
    • /b/匹配,b2N一起阅读。
    • \na不符。
    • b1已打印
  4. b2第二次阅读
    • /b/匹配,aN一起阅读。
    • 匹配\na。附加c2
    • b2\nc2\na已打印。
  5. 以下是实际发生的事情,

    1. a1被阅读并打印。
    2. c1被阅读并打印。
    3. b1已被阅读。
      • /b/匹配,b2N一起阅读。
      • \na不符。
      • b1\nb2已打印
    4. a2已阅读并打印,因为上面已经阅读了b2
    5. 这是一个有效的命令:

      sed -ri '/b/ { :b; N; s/\na/\nc2&/; te; P; D; bb; }; :e;' file
      

      在伪代码中 - 在评论中大致相应的sed部分 - 这是:

      if (input.matches("b")) {                               // /b/ {
        while(true) {                                         // :b
          input += "\n" + readline();                         // N
          if(input.matches("\na")) {                          // s/\na/ ..
            input = input.replace("(\na)", "\nc2\1");         // .. \nc2&/
            goto exit;                                        // te
          }
          print(input.substring(0, input.indexOf('\n'));      // P
          input = input.substring(input.indexOf('\n') + 1);   // D
        }                                                     // bb
      }                                                       // }
      :exit                                                   // :e
      

      翻译回您的数据:

      sed -ri '/^<tr><td><\/td><td class="tdci">/ { :b; N; s/(\n<tr><td><\/td><td>)/\n<tr><td>\&nbsp;<\/td><\/tr>\1/; te; P; D; bb; }; :e' "$f"
      

答案 1 :(得分:2)

@that other guy's excellent answer显示了如何使用sed

然而,sed可能是一个大脑弯曲,当涉及这些问题时,这些问题本质上是程序性的,所以这里的 awk解决方案可能更容易理解

awk -v blockRegex='^<tr><td><\/td><td class="tdci">' \
    -v lineToInsert='<tr><td>\&nbsp;<\/td><\/tr>' \
  '
    # Print a line BEFORE the FIRST line matching `blockRegex`.
  $0 ~ blockRegex { if (!afterFirst) {print lineToInsert; afterFirst=inBlock=1} }
    # Print a line AFTER the LAST (contiguous) line matching `blockRegex`.
  inBlock && $0 !~ blockRegex { print lineToInsert; afterFirst=inBlock=0 }
    # Print the input line.
  { print }
  ' \
  file

请注意,这可以进一步优化,但我希望更简单地澄清逻辑。

  • blockRegex作为变量(带有选项-v)传递给识别要插入行的前后行的连续行 - 带有该行要插入作为变量lineToInsert传入。
  • $0 ~ blockRegex匹配感兴趣的行块中的每一行,如果它是块中的第一行行,则打印要插入的行,如状态变量{ {1}};状态变量afterFirst表示手头的线在感兴趣的块内。
  • inBlock匹配感兴趣的块之后的第一行并打印要插入的行,然后重置状态变量。
  • inBlock && $0 !~ blockRegex只是按原样打印输入行。

请注意,状态变量的使用依赖于print中未初始化的变量默认为awk(在布尔上下文中被视为0;类似地,非零值评估为false)。