使用 head 和 tail 抓取不同的行集并保存到同一个文件中

使用 head 和 tail 抓取不同的行集并保存到同一个文件中

这是作业,但我不会问具体的作业问题。

我需要使用 head 和 tail 从一个文件中获取不同的行集。就像第 6-11 行和第 19-24 行一样,并将它们都保存到另一个文件中。我知道我可以使用附加来做到这一点,例如

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

但我认为我们不应该这样做。
有没有一种特定的方法可以组合 head 和 tail 命令然后保存到文件中?

答案1

如果您使用类似的结构对命令进行分组,您可以使用head单独的基本算术来完成此操作{ ... ; }

{ head -n ...; head -n ...; ...; } < input_file > output_file

所有命令共享相同的输入(谢谢@mikeserv)。
获取第 6-11 行和第 19-24 行相当于:

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

所以,基本上,你会运行:

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file

答案2

您可以使用{ … }分组构造将重定向运算符应用于复合命令。

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

您可以跳过前 M 行并复制接下来的 N 行,而不是复制前 M+N 行并仅保留最后 N 行。这是大文件处理速度明显更快。请注意,+N的参数tail不是要跳过的行数,而是加一 - 它是要打印的第一行的行号,行号从 1 开始。

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

无论哪种方式,输出文件仅打开一次,但输入文件会遍历一次以提取每个片段。如何对输入进行分组?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

一般来说,这是行不通的。 (它可能在某些系统上工作,至少当输入是常规文件时。)为什么?因为输入缓冲。大多数程序(包括tail)不会逐字节读取输入,而是一次读取几千字节,因为这样速度更快。因此tail读取几千字节,在开始时跳过一点,再传递一点到head,然后停止 - 但读取的内容是读取的,并且不可用于下一个命令。

另一种方法是使用head管道/dev/null跳过行。

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

同样,由于缓冲,这不能保证有效。head当输入来自常规文件时,它恰好可以与 GNU coreutils(非嵌入式 Linux 系统上的命令)中的命令一起使用。那是因为一旦这个实现head读取了它想要的内容,它设置文件位置到它没有输出的第一个字节。如果输入是管道,则这不起作用。

从文件中打印多个行序列的一种更简单的方法是调用更通用的工具,例如sed或者awk。 (这可能会比较慢,但这只适用于非常大的文件。)

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1

答案3

我知道你说过你需要使用 head 和 tail,但 sed 绝对是完成这里工作的更简单的工具。

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

您甚至可以使用其他进程在字符串中构建块并通过 sed 运行它。

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n 否定输出,然后用 p 指定要打印的范围,范围的第一个和最后一个数字用逗号分隔。

话虽这么说,您可以执行 @don_crissti 建议的命令分组,也可以循环遍历文件几次,每次遍历时头/尾都会抓取一大块行。

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

文件中的行越多,块越多,sed 的效率就越高。

答案4

使用像这样的 bash 函数:

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

在这种情况下,这有点矫枉过正,但如果你的过滤器变得更大,它可能会成为一个福音。

相关内容