我在同一目录中有多个文件,例如file1
, ...等,每个文件可能包含多行匹配。 我想从每行中删除第 th 行,例如与=和内容匹配file2
PATTERN
N
PATTERN
N
3
file1
1 no match
2 PATTERN
3 same PATTERN
4 no match here
5 no match here either
6 another PATTERN
7 again, no match
8 no
9 last line
预期输出是
1 no match
2 PATTERN
3 same PATTERN
4 no match here
7 again, no match
8 no
就地编辑文件是一种奖励,而不是一项要求(尽管gnu
我知道至少有一种工具可以一次性编辑所有文件......)
有人问了类似的问题这里然而这是一种特殊情况(每个文件中只有一个行匹配模式,并且那里的解决方案仅适用于多行匹配模式,如果它们至少由氮+1 不匹配的行)。
答案1
我相信你可以awk
这样使用:
awk -vN=3 '/PATTERN/ {skips[FNR+N]=1;} {if(!(FNR in skips)) print;}' <file>
所以每次我们点击时,PATTERN
我们都会记录远离这里的行N
,并且只打印那些我们没有标记为跳过的行。
使用 gawk,您也可以使用-i inplace
它来就地完成
正如您所指出的,这不能处理多个文件。当然,您可以用一个for
循环来迭代所有文件,但如果没有足够的文件使命令行太长,您也可以这样做:
awk -vN=3 '{if(FNR==1) split("", skips, ":");} /PATTERN/ {skips[FNR+N]=1;} {if(!(FNR in skips)) print;}' *
skips
每次达到 1 时,我们都会重置为空数组FNR
,即每个文件的开头。
你gnu awk
可以将其写为:
gawk -i inplace 'FNR==1{delete nr};/PATTERN/{nr[FNR+3]++};!(FNR in nr)' file*
答案2
我喜欢 2 遍机制,所以我们可以使用sed -i
:
for file in file1 ...
do sed -i "$file" -e "$(awk <"$file" -v N=3 '/PATTERN/{ print (NR+N) "d" }')"
done
答案3
for f in file1 file2 file...; do
sed -i -f <(grep -n PATTERN "$f" | while IFS=: read line rest; do printf "%dd; " $((line+3)); done) "$f"
done
将其分开:
循环遍历 file1 file2 文件...
在进程替换中构建 sed 表达式,最终针对文件运行。
grep
输出与文件中的 PATTERN 匹配的行号(以及实际匹配的行)。
示例输出:
2:2 PATTERN
3:3 same PATTERN
6:6 another PATTERN
while 循环去掉行号,丢弃匹配的行,然后将其发送到 printf,增加 3
printf 打印目标行号,后跟 sed
d
delete 命令和分隔分号。
示例输出(作为 的输入sed
):
5d; 6d; 9d;
这种方法具有很大的灵活性;您可以设置N=3
并用作$((line+N))
printf 参数。
为了说明就地编辑,我假设 sed 支持-i
“就地”编辑。
答案4
这个用例只是乞求用于使用ex
.
不幸的是,自从删除第三行后给定的行可能会删除包含 PATTERN 的行,从而导致与该行相关的删除被跳过(或更糟糕的是,删除不正确的行),您需要tac
首先使用eg 反转文件。然后删除第三行就可以了前PATTERN 的每个实例,并再次反转文件:
for f in *.txt; do printf %s\\n '%!tac' 'g/PATTERN/-3d' '%!tac' x | ex "$f"; done
如果你有tac
可用的,我认为这是最干净的解决方案。
完全符合 POSIX 标准的解决方案,利用我的回答:
你可以这样做:
for f in *.txt; do printf %s\\n '%!sed -n '\''1h;1\!{x;H;};${g;p;}'\' 'g/PATTERN/-3d' '%!sed -n '\''1h;1\!{x;H;};${g;p;}'\' x | ex "$f"; done
可读性不是很好,但很实用。