【问题标题】:Sed: Better way to address the n-th line where n are elements of an arraySed:解决第 n 行的更好方法,其中 n 是数组的元素
【发布时间】:2018-08-06 17:35:49
【问题描述】:

我们知道 sed 命令会遍历文件的每一行,并且对于每一行,它都会遍历给定的命令列表并执行一些操作。但是当文件非常大时,重复操作的时间和资源成本可能会很可怕。

假设我有一个行号数组,我想将其用作使用 sed 命令删除或打印的地址(例如A=(20000 30000 50000 90000)),并且有一个非常大的目标文件。

最简单的方法可能是: (@John1024 备注,注意每个循环的行号变化)

( for NL in ${A[@]}; do sed "$NL d" $very_large_file; done; )>.temp_file;
cp .temp_file $very_large_file; rm .temp_file

上面代码的问题是,对于数组的每个索引行号,它需要循环整个文件。

为避免这种情况,可以:

#COMM=`echo "${A[@]}" | sed 's/\s/d;/g;s/$/d'`;
#sed -i "$COMM" $very_large_file;
#Edited: Better with direct parameter expansion:
sed -i "${A[@]/%/d;}" $very_large_file;

它首先打印数组并将其SPACE和END_OF_LINE替换为sedd命令,使字符串看起来像"20000d;30000d;50000d;90000d";在第二行,我们将此字符串视为sed 的命令列表。结果是,使用此代码,它只循环文件一次。

此外,对于就地操作(参数-i),即使感兴趣的最大行数已经过去,也不能退出使用qsed,因为如果是这样,那一行之后的行(eg 90001+) 会消失(看来就地操作只是用stdout覆盖文件)。

更好的想法?

(回复@user unknown:) 我认为如果我们在所有索引行都通过后设法“退出”循环,效率会更高。由于上述原因,我们不能使用sed -i。将每一行打印到文件比复制文件花费更多时间(例如cat file1 > file2cp file1 file2)。我们可以从这个概念中受益,使用任何其他方法或工具。这是我所期望的。

PS:这道题的重点是“Lines location”和“Efficiency”; “删除行”操作只是一个例子。对于实际任务,还有更多 - 追加/插入/替换、字段分离、案例判断以及读取/写入文件、计算等。 换句话说,它可能会调用各种操作,创建或不创建子shell,关心变量传递,......所以,使用的工具应该允许我进行线性处理,问题是如何让自己进入感兴趣的线,做各种操作。

感谢任何cmets。

【问题讨论】:

  • 您说得对,sed -i 并没有真正就地写入。它首先创建一个新文件,然后用新文件替换旧文件。有关这方面的详细信息,请参阅this answer
  • 一个关键问题是,即使您只删除文件中的一行,也必须更改后面每一行的字节位置。有没有“删除”行的替代方法?您能否在不更改行长并因此避免移动所有剩余行的情况下注释掉一个(例如,用# 替换行中的第一个字符)?
  • @John1024 是的,我忘记了这个问题,谢谢。对于方法1,存在此问题,您的建议应该有效;或者只是不使用就地参数,而是引用整个代码并将输出重定向到文件。对于方法2,不存在此问题。
  • ‘sed file1 > file1’有未定义的行为。我不确定循环是否修复了它。显然你的第二个解决方案更好。
  • @zzxyz 也许我应该用另一种方式说...... sed without quiet "-n" 将内容打印到标准输出,(通常到屏幕上);但是使用 ( .. ) > 文件,括号中的标准输出重定向到文件。啊,顺便说一句,有输入错误,现在已经编辑了。

标签: arrays bash awk sed


【解决方案1】:

首先复制到一个测试文件以检查结果。 您想对行号进行排序,首先是最高的。

echo "${a[@]}" | sed 's/\s/\n/g' | sort -rn 

您可以使用printf 将命令输入ed

printf "%s\n" "command1" "command2" w q testfile | ed -s testfile

结合这些

printf "%s\n" $(echo "${a[@]}" | sed 's/\s/\n/g' | sort -rn | sed 's/$/d/') w q |
   ed -s testfile

编辑(发送@Ed_Morton):
这可以用更少的步骤编写

printf "%s\n" $(printf '%sd\n' "${a[@]}" | sort -rn ) w q | ed -s testfile

我无法删除sort,因为每个删除指令都是从 1 开始计算行号。
我试图找到一个命令来编辑文件而不重定向到另一个,但我从你应该制作副本的评论开始。我别无选择,我必须支持不需要sort 的直接awk 解决方案。

【讨论】:

  • 令人印象深刻的使用 ed。请问是否会降低计算效率?看来还是需要读取整个文件呢
【解决方案2】:

sed 用于执行 s/old/new,仅此而已,当您在混音中添加一个 shell 循环时,您真的偏离了轨道(请参阅 https://unix.stackexchange.com/q/169716/133219)。要删除编号存储在数组中的行(使用seq 生成输入,因为问题中没有提供示例输入/输出):

$ a=( 3 7 8 )
$ seq 10 |
    awk -v a="${a[*]}" 'BEGIN{split(a,tmp); for (i in tmp) nrs[tmp[i]]} !(NR in nrs)'
1
2
4
5
6
9
10

如果您想在删除最后一个目标行后停止使用 awk 进行处理并让 tail 完成工作,那么您可以预先计算出数组中的最大值,然后只对部分执行 awk最后一个目标行:

max=$( printf '%s\n' "${a[@]}" | sort -rn | head -1 )
head -"$max" file | awk '...' file > out
tail +"$((max+1))" file >> out

idk 如果这真的比让 awk 处理整个文件更快,因为 awk 非常高效,尤其是当您不引用任何字段并且它不进行任何字段拆分时,但您可以给出试试看。

【讨论】:

  • 我没有投反对票(实际上即将投赞成票),但您可能会在您的区块中解释 seq 的目的是向 awk 提供测试数据,以便您可以演示输出。可能完全对你来说很明显,但我在第一次阅读时错过了它。
  • 事实上,我正在使用 sed 对特定行(匹配的行号或正则表达式)进行大量插入/追加/替换,尽管 awk 可以以几乎相同的方式执行此操作。我发现 sed 和 awk 的过程非常相似,除了 sed 具有“保持空间”,这对我来说在减少处理时间方面非常强大 - 尽管它可能与将模式分配给数组相同,因为例子。根据您的建议,我设法认识到其他需要改进的地方。非常感谢,真诚的。
  • @LDecem 不客气。不过,从您的评论来看,我认为您并不真正了解 awk 是什么。 seds 保留空间,除了 s、g 和 p(带有 -n)之外的所有其他 sed 语言结构在 1970 年代中期 awk 被发明时都已过时。 sed 是一个很棒的工具,我几乎每天都在使用它大约 35 年,并且今天仍在使用,但是人们强迫它处理令人费解的单字符符文字符串只会让我摇头,当总是有一个更清晰,更简单,更高效、更容易扩展、更健壮、更便携的 awk 解决方案。
  • @EdMorton 非常正确,我一直在强迫自己从 0 开始在作品中使用 shell 和 bash,这还没有超过 3 个月......我把所有东西都吞了,有很多错误——让人发笑的理解。还有很长的路要走。
  • 小心 sed 问题/答案 - 很多人会发布极其复杂的 sed 解决方案,所以每个人都对他们必须有多么聪明才能弄清楚实际上有一个绝对微不足道的 awk 解决方案,而 sed 解决方案仅用于心理锻炼!很多时候,“sed 解决方案”实际上是 sed+grep+shell+tr+other mash-mash。如果您需要 sed+sed 或 sed+grep 或 grep+grep 或任何其他类似的组合,只需使用 awk 代替。
【解决方案3】:

您可以从您的行中生成一个中间 sed 命令文件。

echo ${A[@]} | sort -n > lines_to_delete
min=`head -1` lines_to_delete
max=`head -1` lines_to_delete
# skip to first and from last line, delete the others
sed -i -e 1d -e ${linecount}d -e 's#$#d#' lines_to_delete
head -${min} input > output
sed -f lines_to_delete input >> output
tail -${max} input >> output
mv output input

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-11-27
    • 1970-01-01
    • 2022-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多