我事先知道行号,并将它们保存在另一个文件中:
cat linenos
2
15
42
44
... etc
如您所见,这些行不相邻,因此我不能将一个范围用于sed
. 目标是修改目标文件行,例如,在它们前面加上像 MARKER 这样的标记
直截了当的方法是sed
多次调用来修改每一行:
for l in $(cat linenos)
do
sed -i "${l}s/^/MARKER/" target_file
done
这显然会多次调用 sed 。
注意:*这种方法不仅效率低下,而且如果修改不是像这样插入标记,它也会使事情出错。任何行删除或插入 sed 命令(如 dar)都会使 linenos 中的初始行号对于循环中的下一个 sed 运行无效。
你有什么建议来改进/优化它?
示例 linenos 文件
cat linenos
2
5
示例目标文件
cat target_file
line one
line two
line three
line four
line five
line six
修改后的 target_file 的预期结果
cat target_file
line one
MARKERline two
line three
line four
MARKERline five
line six
我想出的可能方法是动态创建 sed 场景
SEDCMD=$(for l in $(cat linenos); do echo -n "${l}s/^/MARK/;" ; done)
sed -i -e "$SEDCMD" targetfile
@steeldriver 的以下方法分享了这个想法,但更加优雅和简洁
-f
您可以使用 sed 本身(或您选择的其他文本处理实用程序)将行号转换为 sed 表达式,然后使用开关将它们传递给 sed前任。
这至少只调用 sed两次。
使用
perl
(GNU的sed
来源-i
):我们在 的标准输入上提供行号列表
perl
。BEGIN
这是在块中读取的。对于每一行输入,我们将该行转换为带有 的数字
0+$_
。这使得换行符消失并且还规范化了数字(所有 1e0、1、01 变为 1)。%l
散列表填充有每个行号的值作为1
键。将
target_file
在主-p
循环中处理,其中MARKERS
添加到当前行号 ($.
)所在行%l
的非零值。或节省一点内存:
如果您想要“就地”编辑(与 perl 和 GNU sed 相同
-i
),请使用 GNU awk 并在语句之前awk '...'
更改awk -i inplace '...'
并添加一个,这样您的文件就不会被清空。恕我直言,使用任何 awk(或任何其他 UNIX 工具)执行此操作更简单:print;
next
linenos
if
fileN
包含要修改的行数,andtarget_file
是包含要修改的行的文本文件。最低限度的解决方案将需要读取每个文件一次。已排序
如果包含行号的文件每行包含一个数字(大于 1),已排序并且没有重复,我们可以使用:
这将只在内存中保留一行(每个文件)并从头到尾遍历两个文件。但是,一旦 awk 处理了一行,例如第 15 行,它就不会返回到第 12 行。因此,必须对文件
lineN
进行排序(不重复,并且大于 1)才能正常工作。未分类
当然,天真的解决方案是可以对行号文件进行排序
sort -nu fileN
。但是,如果行号列表可能未排序(并且重复),我们可以使用 sed
ed
( 的前身sed
)或 awk (稍后):将每一行转换
lineN
为 sed 编辑命令,例如s/^/MARKER /
. shell printf 或 sed 都可以做到这一点:请注意,在最后一种情况下,编辑是直接在原始文件中完成的。最后一个命令
w
将修改写入文件。如果需要打印结果,则使用第三个选项,它将打印所有行。awk
fileN
在awk中,在内存和进程中捕获整体target_file
或者,使用一个变量来控制带有行号的文件列表何时结束:
请注意,最后一个版本允许多个带有行号的文件,例如示例中的
fileN
和fileK
。另请注意,awk 版本不处理重复的行号。所有重复的行号只处理一次。
另一种方式,使用
ed
而不是sed
修改target_file
就地: