如何使用 sed 在 FIRST 和 LAST 匹配模式之前插入一行

How to insert a line before the FIRST and LAST matching pattern using sed

我有一个文件:users.txt 如下所示:

    Surname
    Surname
    Surname
    Age
    Age
    Age

我需要添加 ONE 行文本:Name ABOVE 无论我在哪里找到 FIRST 出现 Surname 使文件看起来像:

    Name
    Surname
    Surname
    Surname
    Age
    Age
    Age

我还需要能够添加 ONE 行文本:Gender BELOW 无论我在哪里找到30=]LAST 次 Age 使文件看起来像:

    Surname
    Surname
    Surname
    Age
    Age
    Age
    Gender

目前我有这些 sed 命令:

sed '/Surname/i \Name' user.txt
sed '/Age/a \Gender' users.txt

尽管他们在每个 找到的匹配项上添加新行。

非常感谢您的宝贵时间和协助。

使用 awk:

awk '/Surname/ && !s++ { print "Name" } /Age/ { a = 1; print; next } a && !f++ { print "Gender" } 1; END { if(a && !f) { print "Gender" } }' filename

其工作原理如下:

/Surname/ && !s++ {    # If Surname is found in a line and the flag for it
                       # is not set (i.e., if this is the first surname line),
                       # set flag and
  print "Name"         # print "Name"
}
/Age/ {                # if Age is found
  a = 1                # set flag for it
  print                # print the line
  next                 # and do nothing else for this line
}
a && !f++ {            # if the Age flag is set and (implicitly here) Age
                       # is not found in the line, and the flag that we
                       # already did this is not yet set, set the flag, and
  print "Gender"       # print Gender
}
1                      # then, finally, print the line from the input.

END {                  # if we reach the end without having printed the 
  if(a && !f) {        # gender line but found an Age block (that is, when the
                       # age block is at the end of the file)
    print "Gender"     # print it then.
  }
}

!s++ 东西是一种检查标志是否未设置并一次性设置的方法;有人告诉我这是惯用的 awk。如果觉得不爽,也可以写

/Surname/ && !s { s = 1; print "Name" }

以此类推效果相同。

awk 会更容易。每当我听到 "do something on the LAST ..." 我想 "reverse the file, and do something on the FIRST ..."

file=users.txt
tmpfile=$(mktemp)

tac "$file" | 
awk '/Age/ && !found {print "Gender"; found=1} 1' | 
tac | 
awk '/Surname/ && !found {print "Name"; found=1} 1' > "$tmpfile" &&
mv "$tmpfile" "$file"

实际上 sed 并没有那么糟糕:

sed '
  /Surname/ {
    # we have seen the first pattern, insert the 1st new line
    i Name
    :a
    N                         # append the next line to pattern space
    $ {                       # if this is the last line
      s/.*Age\n/&Gender\n/    # add the 2nd new line after the last Age
      bb                      # and we are done: goto b
    }
    ba                        # goto a
    :b
  }
' users.txt 

Awk
设置第一次出现的姓氏和最后一次出现的年龄的记录数,读取文件两次并在达到设置的记录数时打印内容。

awk '/Surname/ && !s++{Sr=NR}/Age/{FNR==NR&&age=NR}
     FNR!=NR{print (Sr==FNR?"Name\n"[=10=]:age==FNR?[=10=]"\nGender":[=10=])}' test{,}

Name
    Surname
    Surname
    Surname
    Age
    Age
    Age
Gender

有很多方法可以做到这一点,但我更喜欢一种方法,因为它具有健壮性、灵活性、清晰性、简单性、没有代码重复等优点,它是一次性识别目标行,然后在第二遍:

gawk '
    NR==FNR { if (!first[[=10=]]) first[[=10=]]=NR; last[[=10=]]=NR; next }
    FNR==first["Surname"] { print "Name" }
    { print }
    FNR==last["Age"] { print "Gender" }
' file file

示例如何在 Perl 中执行此操作

perl -nE'/Surname/&&($n++||say"Name")||($n=0);/Age/&&($g=1)||($g--&&say"Gender");print}{say"Gender"if$g'

另一种方式

perl -nE'/Surname/&&say("Name")..!/Surname/;/Age/&&($g=1)||($g--&&say"Gender");print}{say"Gender"if$g'

这是在最后一场比赛之前用 sed 插入一行的方法。

sed -i -e '/**PATTERN**[^\n]*/,$!b;//{x;//p;g};//!H;$!d;x;i**TEXTOINSERT**' FILE

这是在第一次匹配之前使用 sed 插入一行的方法。

sed -i -e '/**PATERN**/{a\**TEXTOINSERT**' -e ':a;n;ba}' FILE