如何使用 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
我有一个文件: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