sed 头痛:在文件中的单个匹配项上插入行(不是每行)
sed headaches: inserting lines upon singular matches in file (NOT per line)
经过八个多小时的搜索,我认输并为此创建了一个新问题。操作很简单,但我很难让它正常工作,似乎已经通过了 SO 上的所有其他解决方案。我需要两件事:
1.) 在整个文件中PBS
的FIRST MATCH 出现的行之前插入一行。它应该在整个文件中只发生一次。出于某种原因,我尝试过的每个解决方案最终都会为文件中的每个事件复制插入;我怀疑,因为 sed 是逐行跟踪的。
所以这需要发生。原始文件:
stuff here
stuff here
PBS -N
PBS -V
stuff here
变为:
stuff here
stuff here
**inserted line**
PBS -N
PBS -V
stuff here
2.) 在整个文件中出现"PBS" 的LAST MATCH 的行之后追加一行。和以前一样:它应该在整个文件中只发生一次。
所以这需要发生:
stuff here
stuff here
PBS -N
PBS -V
stuff here
变为:
stuff here
stuff here
PBS -N
PBS -V
**inserted line**
stuff here
我在网上看到的所有解决方案(此时我打开了大约二十个选项卡)都表明这应该相对容易。我毫不羞愧地宣布 sed 在这一点上正在损害我的自尊...感谢任何可以提供帮助的人
这里有三种方法,两种使用sed,一种使用awk。
单独使用 sed
在第一次出现之前插入一次
$ sed ':a;$!{N;ba}; s/PBS/inserted line\nPBS/' file
stuff here
stuff here
inserted line
PBS -N
PBS -V
stuff here
在最后一次出现后插入一次:
$ tac file | sed ':a;$!{N;ba}; s/PBS/inserted line\nPBS/' | tac
stuff here
stuff here
PBS -N
PBS -V
inserted line
stuff here
工作原理
:a;$!{N;ba};
这会一次读入整个文件。 (如果整个文件很大,你会想看看其他方法之一。)
s/PBS/inserted line\nPBS/
这会执行替换。
tac
通常,在我们读入整个文件之前,没有办法知道文件中最后出现的 PBS。然而,tac
颠倒了行的顺序。因此,最后的变成了第一个。
使用 awk
awk 的主要优势在于它允许轻松使用变量。在这里,我们创建一个标志 f
,在我们到达第一次出现 PBS 后将其设置为 true:
$ awk '/PBS/ && !f {print "inserted line"; f=1} 1' file
stuff here
stuff here
inserted line
PBS -N
PBS -V
stuff here
要在最后一次出现之后插入,我们可以使用上述 tac
解决方案。为了多样化,这种方法分两次读取文件。在第一个 运行 上,它跟踪 PBS 的最后行号。第二,它打印需要打印的内容:
$ awk 'NR==FNR{if (/PBS/)n=FNR;next} 1{print} n==FNR {print "inserted line"}' file file
stuff here
stuff here
PBS -N
PBS -V
inserted line
stuff here
这些 awk 解决方案一次处理一行文件。如果文件非常大,这有助于限制内存使用。
使用 grep 和 sed
另一种方法是使用grep
来告诉我们需要处理的行号。在第一次出现之前插入:
$ sed "$(grep -n PBS file | cut -d: -f1 | head -n1)"' s/PBS/inserted line\nPBS/' file
stuff here
stuff here
inserted line
PBS -N
PBS -V
stuff here
这在最后一个之后插入:
$ sed "$(grep -n PBS file | cut -d: -f1 | tail -n1)"' s/.*PBS.*/&\ninserted line/' file
stuff here
stuff here
PBS -N
PBS -V
inserted line
stuff here
这种方法不需要一次将整个文件读入内存。
@John1924 回答得很好。在这种情况下,您也可以不以有效的方式完成任务,例如:
- 仅打印第一个 PBS 之前的行
- 回显行
- 仅打印(包括)第一个 PBS 之后的行
例如。当 ./pbsfile
中有以下内容时
line 1
line 2
PBS -N first
PBS -N second
line 3
PBS -V last-1
PBS -V last
line 4
line 5
上面的例子可以做到:
pbsfile="./pbsfile"
(
#delete the lines after the 1st PBS
#so remains only the lines before the 1st PBS
sed '/PBS/,$d' "$pbsfile"
#echo the needed line
echo "THIS SOULD BE INSERTED BEFORE 1st PBS"
#print only the lines after the 1st PBS
sed -n '/PBS/,$p' "$pbsfile"
)
产生:
line 1
line 2
THIS SOULD BE INSERTED BEFORE 1st PBS
PBS -N first
PBS -N second
line 3
PBS -V last-1
PBS -V last
line 4
line 5
同上,最后一个PBS也可以,将sed前后的文件倒过来即可,例如以下
pbsfile="./pbsfile"
(
tail -r "$pbsfile" | sed -n '/PBS/,$p' | tail -r
echo "THIS SOULD BE INSERTED AFTER THE LAST PBS"
tail -r "$pbsfile" | sed '/PBS/,$d' | tail -r
)
什么产生
line 1
line 2
PBS -N first
PBS -N second
line 3
PBS -V last-1
PBS -V last
THIS SOULD BE INSERTED AFTER THE LAST PBS
line 4
line 5
同样,这仅适用于 "alternative solution"(无效)。
另一种 sed 方法:
sed '/PBS/ {
# insert the new line
i\
inserted line
# then loop over the rest of the file, implicitly printing each line
:a; n; ba
}' file
对于last匹配,此版本不需要tac
sed '
# read the whole file into pattern space
:a; $!{N;ba}
# then, use greedy matching to get to the *last* PBS
# and non-greedy matching to get to the end of that line.
s/.*PBS[^\n]*/&\ninserted line/
' file
sed 是用于此类工作的错误工具,它用于对各行进行简单替换。只需使用 awk:
$ cat tst.awk
NR == FNR { if (/PBS/) hits[++numHits] = NR; next }
FNR == hits[1] { print "inserted line before" }
{ print }
FNR == hits[numHits] { print "inserted line after" }
$ awk -f tst.awk file file
stuff here
stuff here
inserted line before
PBS -N
PBS -V
inserted line after
stuff here
这是一个只读取文件一次的awk
:
cat file
line 1
line 2
PBS -N first
PBS -N second
line 3
PBS -V last-1
PBS -V last
line 4
line 5
awk '/PBS/ {last=NR;if (!f) {first=NR;f=1}} {a[NR]=[=11=]} END {for (i=1;i<=NR;i++) {if (i==first) a[i]="new line before\n"a[i];if (i==last) a[i]=a[i]"\nnew line after";print a[i]}}' file
line 1
line 2
new line before
PBS -N first
PBS -N second
line 3
PBS -V last-1
PBS -V last
new line after
line 4
line 5
工作原理:
awk ' # Start
/PBS/ { # Does line contains "PBS"
last=NR # Set last to current line number
if (!f) { # Is flag "f" false
first=NR # Yes, set first line to current line
f=1}} # and set flag "f"
{
a[NR]=[=12=]} # Store alle line in array "a"
END {
for (i=1;i<=NR;i++) { # Loop trough all lines
if (i==first) # Is line number equal to first hits
a[i]="new line before\n"a[i] # Add data before line
if (i==last) # Is line number equal to last hits
a[i]=a[i]"\nnew line after" # Add data after line
print a[i]}} # Print the line
' file
要让 sed 正确执行它,您必须绕过它的每行操作,然后使用原始正则表达式重新设置它。不难,就是有点麻烦
sed -E 'H;$!d;g
s/\n[^\n]*PBS/\ninsert before first PBS-containing line&/
s/.*PBS[^\n]*/&\ninsert after last PBS-containing line/;
s/.//
'
H;$!d;g
将整个文件拖到保持缓冲区,前面有一个额外的换行符(H
将当前行附加到保持缓冲区,前面有 \n
,如果这不是最后一行,$!d
将被删除;g
(及其后面的内容)仅在最后一行运行并检索保留缓冲区。
因此 s/\n[^\n]*PBS
将找到第一个 PBS 之前的换行符,因为每行之前总是有一个换行符,s/.*PBS[^\n]*/
将找到最后一个 PBS 以及任何后续换行符之前的所有内容,并且s/.//
去掉我们卡在那里的人工换行符,使第一次出现的搜索工作。
请注意,您可以使第一次出现的插入对任意 n 有效,方法是将其附加到搜索中,s/\n[^\n]*PBS/\netc&/4
对第四次出现。
经过八个多小时的搜索,我认输并为此创建了一个新问题。操作很简单,但我很难让它正常工作,似乎已经通过了 SO 上的所有其他解决方案。我需要两件事:
1.) 在整个文件中PBS
的FIRST MATCH 出现的行之前插入一行。它应该在整个文件中只发生一次。出于某种原因,我尝试过的每个解决方案最终都会为文件中的每个事件复制插入;我怀疑,因为 sed 是逐行跟踪的。
所以这需要发生。原始文件:
stuff here
stuff here
PBS -N
PBS -V
stuff here
变为:
stuff here
stuff here
**inserted line**
PBS -N
PBS -V
stuff here
2.) 在整个文件中出现"PBS" 的LAST MATCH 的行之后追加一行。和以前一样:它应该在整个文件中只发生一次。
所以这需要发生:
stuff here
stuff here
PBS -N
PBS -V
stuff here
变为:
stuff here
stuff here
PBS -N
PBS -V
**inserted line**
stuff here
我在网上看到的所有解决方案(此时我打开了大约二十个选项卡)都表明这应该相对容易。我毫不羞愧地宣布 sed 在这一点上正在损害我的自尊...感谢任何可以提供帮助的人
这里有三种方法,两种使用sed,一种使用awk。
单独使用 sed
在第一次出现之前插入一次
$ sed ':a;$!{N;ba}; s/PBS/inserted line\nPBS/' file
stuff here
stuff here
inserted line
PBS -N
PBS -V
stuff here
在最后一次出现后插入一次:
$ tac file | sed ':a;$!{N;ba}; s/PBS/inserted line\nPBS/' | tac
stuff here
stuff here
PBS -N
PBS -V
inserted line
stuff here
工作原理
:a;$!{N;ba};
这会一次读入整个文件。 (如果整个文件很大,你会想看看其他方法之一。)
s/PBS/inserted line\nPBS/
这会执行替换。
tac
通常,在我们读入整个文件之前,没有办法知道文件中最后出现的 PBS。然而,
tac
颠倒了行的顺序。因此,最后的变成了第一个。
使用 awk
awk 的主要优势在于它允许轻松使用变量。在这里,我们创建一个标志 f
,在我们到达第一次出现 PBS 后将其设置为 true:
$ awk '/PBS/ && !f {print "inserted line"; f=1} 1' file
stuff here
stuff here
inserted line
PBS -N
PBS -V
stuff here
要在最后一次出现之后插入,我们可以使用上述 tac
解决方案。为了多样化,这种方法分两次读取文件。在第一个 运行 上,它跟踪 PBS 的最后行号。第二,它打印需要打印的内容:
$ awk 'NR==FNR{if (/PBS/)n=FNR;next} 1{print} n==FNR {print "inserted line"}' file file
stuff here
stuff here
PBS -N
PBS -V
inserted line
stuff here
这些 awk 解决方案一次处理一行文件。如果文件非常大,这有助于限制内存使用。
使用 grep 和 sed
另一种方法是使用grep
来告诉我们需要处理的行号。在第一次出现之前插入:
$ sed "$(grep -n PBS file | cut -d: -f1 | head -n1)"' s/PBS/inserted line\nPBS/' file
stuff here
stuff here
inserted line
PBS -N
PBS -V
stuff here
这在最后一个之后插入:
$ sed "$(grep -n PBS file | cut -d: -f1 | tail -n1)"' s/.*PBS.*/&\ninserted line/' file
stuff here
stuff here
PBS -N
PBS -V
inserted line
stuff here
这种方法不需要一次将整个文件读入内存。
@John1924 回答得很好。在这种情况下,您也可以不以有效的方式完成任务,例如:
- 仅打印第一个 PBS 之前的行
- 回显行
- 仅打印(包括)第一个 PBS 之后的行
例如。当 ./pbsfile
line 1
line 2
PBS -N first
PBS -N second
line 3
PBS -V last-1
PBS -V last
line 4
line 5
上面的例子可以做到:
pbsfile="./pbsfile"
(
#delete the lines after the 1st PBS
#so remains only the lines before the 1st PBS
sed '/PBS/,$d' "$pbsfile"
#echo the needed line
echo "THIS SOULD BE INSERTED BEFORE 1st PBS"
#print only the lines after the 1st PBS
sed -n '/PBS/,$p' "$pbsfile"
)
产生:
line 1
line 2
THIS SOULD BE INSERTED BEFORE 1st PBS
PBS -N first
PBS -N second
line 3
PBS -V last-1
PBS -V last
line 4
line 5
同上,最后一个PBS也可以,将sed前后的文件倒过来即可,例如以下
pbsfile="./pbsfile"
(
tail -r "$pbsfile" | sed -n '/PBS/,$p' | tail -r
echo "THIS SOULD BE INSERTED AFTER THE LAST PBS"
tail -r "$pbsfile" | sed '/PBS/,$d' | tail -r
)
什么产生
line 1
line 2
PBS -N first
PBS -N second
line 3
PBS -V last-1
PBS -V last
THIS SOULD BE INSERTED AFTER THE LAST PBS
line 4
line 5
同样,这仅适用于 "alternative solution"(无效)。
另一种 sed 方法:
sed '/PBS/ {
# insert the new line
i\
inserted line
# then loop over the rest of the file, implicitly printing each line
:a; n; ba
}' file
对于last匹配,此版本不需要tac
sed '
# read the whole file into pattern space
:a; $!{N;ba}
# then, use greedy matching to get to the *last* PBS
# and non-greedy matching to get to the end of that line.
s/.*PBS[^\n]*/&\ninserted line/
' file
sed 是用于此类工作的错误工具,它用于对各行进行简单替换。只需使用 awk:
$ cat tst.awk
NR == FNR { if (/PBS/) hits[++numHits] = NR; next }
FNR == hits[1] { print "inserted line before" }
{ print }
FNR == hits[numHits] { print "inserted line after" }
$ awk -f tst.awk file file
stuff here
stuff here
inserted line before
PBS -N
PBS -V
inserted line after
stuff here
这是一个只读取文件一次的awk
:
cat file
line 1
line 2
PBS -N first
PBS -N second
line 3
PBS -V last-1
PBS -V last
line 4
line 5
awk '/PBS/ {last=NR;if (!f) {first=NR;f=1}} {a[NR]=[=11=]} END {for (i=1;i<=NR;i++) {if (i==first) a[i]="new line before\n"a[i];if (i==last) a[i]=a[i]"\nnew line after";print a[i]}}' file
line 1
line 2
new line before
PBS -N first
PBS -N second
line 3
PBS -V last-1
PBS -V last
new line after
line 4
line 5
工作原理:
awk ' # Start
/PBS/ { # Does line contains "PBS"
last=NR # Set last to current line number
if (!f) { # Is flag "f" false
first=NR # Yes, set first line to current line
f=1}} # and set flag "f"
{
a[NR]=[=12=]} # Store alle line in array "a"
END {
for (i=1;i<=NR;i++) { # Loop trough all lines
if (i==first) # Is line number equal to first hits
a[i]="new line before\n"a[i] # Add data before line
if (i==last) # Is line number equal to last hits
a[i]=a[i]"\nnew line after" # Add data after line
print a[i]}} # Print the line
' file
要让 sed 正确执行它,您必须绕过它的每行操作,然后使用原始正则表达式重新设置它。不难,就是有点麻烦
sed -E 'H;$!d;g
s/\n[^\n]*PBS/\ninsert before first PBS-containing line&/
s/.*PBS[^\n]*/&\ninsert after last PBS-containing line/;
s/.//
'
H;$!d;g
将整个文件拖到保持缓冲区,前面有一个额外的换行符(H
将当前行附加到保持缓冲区,前面有 \n
,如果这不是最后一行,$!d
将被删除;g
(及其后面的内容)仅在最后一行运行并检索保留缓冲区。
因此 s/\n[^\n]*PBS
将找到第一个 PBS 之前的换行符,因为每行之前总是有一个换行符,s/.*PBS[^\n]*/
将找到最后一个 PBS 以及任何后续换行符之前的所有内容,并且s/.//
去掉我们卡在那里的人工换行符,使第一次出现的搜索工作。
请注意,您可以使第一次出现的插入对任意 n 有效,方法是将其附加到搜索中,s/\n[^\n]*PBS/\netc&/4
对第四次出现。