如何使用 sed、grep 或 awk 根据另一个文件中的行号保留文件中的某些行

如何使用 sed、grep 或 awk 根据另一个文件中的行号保留文件中的某些行

我有两个文件。File1包含一些句子,并File2包含我想要保留的行号File1

例如,File1

He is a boy.
She is a cook.
Okay.
She went to school.
She is pretty.

File2

1
4

输出:

He is a boy.
She went to school.

有没有办法可以使用sed, grep, 或 来做到这一点awk?我不想手动将行号写为这里

答案1

我们可以将数字列表转换为命令序列,并在一次调用中将sed它们作为编辑脚本运行:sedsed

sed 's/$/p/' lines.list | sed -n -f /dev/stdin file.txt

在这里,第一个sed创建一个sed由诸如 等命令组成的脚本1p4p只需p在每行末尾插入即可。然后,该脚本被发送到sed管道之后的第二个,该管道读取该脚本-f /dev/stdin并将其与文本文件作为输入一起应用。

这需要每个文件只读取一次。


使用awk,将行号作为键读入关联数组,然后在读取另一个文件时,查看当前行号是否是之前在数组中作为键的行号之一:

awk 'FNR == NR { lines[$0]; next } (FNR in lines)' lines.list file.txt

在 中awk,特殊变量NRFNR分别是迄今为止读取的记录(行)总数和当前文件中读取的记录(行)总数。如果NR等于FNR,我们将从第一个输入文件中读取,并使用当前行 ,$0作为键(未给出值)创建一个数组条目,并立即跳到下一行输入。

如果我们不是从当前行读取,我们会进行测试,FNR in lines看看FNR当前文件中的行号是否是名为 的数组中的键lines。如果是,则将打印当前行。


如果没有其他工具的大力支持,该grep实用程序并不是真正用于执行此类任务的。它从内容与给定模式匹配(或不匹配)的文本文件中提取行。因此,模式应该与行匹配,而不是行号。

以下内容仅供娱乐,不应被视为如何实际解决此问题的建议。

你可以插入行号与grep使用

grep -n '.*' file.txt

这将在文件中所有行的开头插入行号,紧接着是:该行的原始内容。

然后,与sed解决方案一样,我们可以修改模式文件以使其与这些特定数字的选择相匹配:

sed 's/.*/^&:/' lines.list

这将输出正则表达式,例如^1:^4:,每个正则表达式与行开头的特定行号匹配。

然后我们可以grep使用这些表达式(这里借助过程替换)。最后,我们使用以下命令删除临时行号cut

grep -n '.*' file.txt | grep -f <(sed 's/.*/^&:/' lines.list) | cut -d : -f 2-

...但这太做作了,甚至不能被认为是一个合理的解决方案。


上述每个解决方案将始终按照所选行在文本文件中出现的顺序显示它们。如果您想按照行号文件中出现的顺序输出行,那么您可以使用sed(或awk,请参阅下文):

sed 's/$/p/' lines.list | ed -s file.txt

p再次,我们通过简单地在每行末尾添加来从行号文件创建编辑脚本。

然后,该脚本作为命令输入传递到ed编辑器,编辑器将命令按顺序应用于文本文件。

测试:

$ cat lines.list
4
1
$ sed 's/$/p/' lines.list | ed -s file.txt
She went to school.
He is a boy.

请注意,将sed整个文件读入内存,就像下面的等效awk程序一样:

awk 'NR == FNR { lines[FNR] = $0; next } { print lines[$0] }' file.txt lines.list

请注意,与之前的解决方案相比,输入文件已切换awk。这允许我们首先将文本文件lines逐行读入数组,然后在读取带有行号的文件时随机从中选择行。

答案2

假设您的文件是file.txt并且lines.txt包含行号。使用xargs

# extract digit sequences from lines.txt and make sed arguments
sed 's/[^[:digit:]]*\([[:digit:]]\+\)[^[:digit:]]*/-e \1p /g' lines.txt \
    | xargs /bin/sh -c '[ $# -gt 0 ] && sed -n "$@" file.txt' sh

答案3

使用(以前称为 Perl_6)

#Sample Input:

~$ cat data.txt
He is a boy.
She is a cook.
Okay.
She went to school.
She is pretty.

显示索引的连续示例:

~$ raku -e '.put for lines[ 0,3 ];'  data.txt
He is a boy.
She went to school.

#Take line-number index inline (subtract 1 to make zero-indexed):

~$ raku -e 'my @index = <1 4>; .put for lines[ @index.map: *-1 ];' data.txt
He is a boy.
She went to school.

将索引作为文件路径,从命令行获取数据:

~$ raku -e 'my @ind = "/path/to/index.txt".IO.lines; 
            .put for lines[ @index.map: *-1 ];'  data.txt
He is a boy.
She went to school.

将这两个文件从命令行中取出(data.txt然后index.txt):

~$  raku -e 'my @data  = @*ARGS[0].IO.lines;
             my @index = @*ARGS[1].IO.lines;
             .put for @data[ @index.map: *-1 ];'  data.txt  index.txt
He is a boy.
She went to school.

https://docs.raku.org
https://raku.org

答案4

如果 File2 中的目标行号按递增顺序排列,则可以使用此方法。

sed -e 's/$/b/;$a d' < File2 |
sed -f - File1

生成一系列 sed 命令

1b
4b
d

在按照 File2 中给出的顺序打印 File1 中的行的一般情况下,我们使用下面给出的两者之一。

使用 awk,我们生成一个关联数组,该数组以 File1 中的行号和值作为该行内容。但仅保存 File2 中提到的行以及不匹配的额外行。维护另一个数组,该数组以数字递增索引为键,从 Fikle2 中的目标行号开始,并将值字段作为目标行号。

awk '
{if (X) c[b[FNR]]=$0; else b[a[NR]=$1]=$1}
END {for (i=1; i in a; i++) print c[a[i]]}
' File2 X=1 File1

在 slurp 模式 -z 下使用 GNU sed。但首先,我们生成查看 File2 内容的命令

sed -e 's:.*:s/^(.*\\n){&}/\\1\\d0/M;P;g:' File2 |
sed -zEn -e h -f - File1 | tr -d '\0'

相关内容