使用标题行分割文件
1. 概述
正如我们所知,*split *命令可以帮助我们将一个大文件按给定的行数拆分成多个小文件。
但是,如果输入文件包含标题行,我们有时希望将标题行复制到每个拆分文件中。默认情况下,split命令无法做到这一点。
在本教程中,我们将讨论如何解决这个问题。
2. 问题介绍
一个具体的例子可以帮助我们快速理解问题。
首先,让我们看一下我们的输入示例。tokyo_medal.tsv文件保存了东京奥运会奖牌表前 10 名的数据:
$ cat tokyo_medal.tsv
Rank Country Gold Silver Bronze Total
1 United States 39 41 33 113
2 China 38 32 18 88
3 Japan 27 14 17 58
4 Great Britain 22 21 22 65
5 ROC 20 28 23 71
6 Australia 17 7 22 46
7 Netherlands 10 12 14 36
8 France 10 12 11 33
9 Germany 10 11 16 37
10 Italy 10 10 20 40
正如我们在上面的输出中看到的,该文件是一个TSV 文件 。此外,该文件包含一个标题行来说明每列中值的含义。TSV 或 CSV 文件包含标题行是很常见的。
现在,我们的目标是将tokyo_medal.tsv文件拆分成多个部分。假设我们希望每件作品都有三个记录。此外,每件作品还必须有一个标题行。
在本教程中,我们将介绍三种不同的方法来解决问题:
- 使用版本 >= 8.13 的split 命令
- 编写一个简单的 shell 脚本
- 使用 awk 命令
接下来,让我们看看它们的实际效果。
3. 使用较新的split命令
split命令是GNU Coreutils 包的成员。
从 8.13 版本开始,split实用程序引入了一个新的*–filter=COMMAND* 选项。
我们将使用此选项解决问题。首先,我们将看一下完成这项工作的命令。然后,我们就会明白为什么它会起作用。
3.1. 解决方案
**–filter =COMMAND 选项允许我们将split结果写入 shell 命令。**换句话说,我们可以使用filter命令对拆分后的部分进行后处理。
接下来,让我们看看这个选项如何帮助我们解决问题:
$ tail -n +2 tokyo_medal.tsv | split -d -l 3 - --filter='sh -c "{ head -n1 tokyo_medal.tsv; cat; } > $FILE"' part_
$ ls -1 part*
part_00
part_01
part_02
part_03
正如我们在上面的输出中看到的,执行命令后创建了四个文件。现在,让我们检查文件的内容:
$ head part*
==> part_00 <==
Rank Country Gold Silver Bronze Total
1 United States 39 41 33 113
2 China 38 32 18 88
3 Japan 27 14 17 58
==> part_01 <==
Rank Country Gold Silver Bronze Total
4 Great Britain 22 21 22 65
5 ROC 20 28 23 71
6 Australia 17 7 22 46
==> part_02 <==
Rank Country Gold Silver Bronze Total
7 Netherlands 10 12 14 36
8 France 10 12 11 33
9 Germany 10 11 16 37
==> part_03 <==
Rank Country Gold Silver Bronze Total
10 Italy 10 10 20 40
所以,我们得到了预期的结果。这样,我们就解决了这个问题。
3.2. 命令如何工作
现在,让我们浏览命令的每个部分并了解其工作原理:
tail -n +2 tokyo_medal.tsv | split -d -l 3 - --filter='sh -c "{ head -n1 tokyo_medal.tsv; cat; } > $FILE"' part_
- tail -n +2 tokyo_medal.tsv – tail 命令从输入文件中剪切标题行,然后我们将所有数据记录通过管道传递给下一个命令
- …… | split -d -l 3 – –filter=’…’ part_ – 让我们先跳过*–filter=’…‘部分。*split命令从stdin ( - )读取数据记录,并每三行 ( -l 3 ) 将它们拆分。-d选项告诉split在生成的文件名中使用数字后缀
- –filter=‘sh -c “{ head -n1 tokyo_medal.tsv; 猫; } > $FILE”’ – –filter选项中的命令对拆分数据进行后处理。**我们用head和cat命令声明了一个命令组。 它从输入文件中读取标题行,然后加入拆分记录。最后,我们将带有标题行的记录重定向到*$FILE*,即part_x文件
但是,如果我们系统上的Coreutils包的版本低于8.13,我们需要通过不同的方式来解决问题。因此,我们现在将注意力转向其他一些方法。
4. 编写一个简单的 Shell 脚本
尽管较旧的split命令无法自行解决问题,但我们可以用 shell 脚本将其包装起来以处理标题行。
4.1. 解决问题
简单来说,我们可以分两步解决问题:
- 第 1 步:拆分没有标题行的输入文件
- 第 2 步:为每个拆分文件添加标题行
按照这个思路,我们可以构建一个脚本:
#!/bin/bash
INPUT=tokyo_medal.tsv
# Step 1: split the input file without the header line
tail -n +2 "$INPUT" | split -d -l 3 - sh_part_
# Step 2: add the header line to each split file
for file in sh_part_*
do
head -n 1 "$INPUT" > with_header_tmp
cat "$file" >> with_header_tmp
mv -f with_header_tmp "$file"
done
如脚本所示,当我们实现步骤 2 时,我们创建了一个带有_header_tmp 的临时文件来保存标题行,然后附加split结果。
请注意,此示例脚本中跳过了参数处理。例如,输入文件和拆分选项在脚本中是硬编码的。
那是因为本教程的重点是文件拆分实现。但是,如果我们想让我们的脚本可重用,我们应该在现实世界中添加参数处理。
4.2. 测试脚本
现在,让我们将脚本命名为split_with_header.sh并测试它是否按预期工作:
$ ./split_with_header.sh
$ head sh_part_*
==> sh_part_00 <==
Rank Country Gold Silver Bronze Total
1 United States 39 41 33 113
2 China 38 32 18 88
3 Japan 27 14 17 58
==> sh_part_01 <==
Rank Country Gold Silver Bronze Total
4 Great Britain 22 21 22 65
5 ROC 20 28 23 71
6 Australia 17 7 22 46
==> sh_part_02 <==
Rank Country Gold Silver Bronze Total
7 Netherlands 10 12 14 36
8 France 10 12 11 33
9 Germany 10 11 16 37
==> sh_part_03 <==
Rank Country Gold Silver Bronze Total
10 Italy 10 10 20 40
伟大的!我们的脚本有效。 通常,当我们遇到文件拆分问题时,split命令会首先出现。但是,实际上,其他 Linux 命令也可以执行这种文件拆分任务。
接下来,让我们使用awk命令解决问题。
5. 使用awk命令
awk是文本处理的有力武器。此外,awk还定义了自己的类 C 脚本语言。它可以在不使用任何外部命令的情况下解决这个问题。
5.1. awk解决方案
首先,让我们看看awk是如何解决问题的:
$ awk -v lines="3" -v pre="awk_part_" '
NR==1 { header=$0; next}
(NR-1) % lines ==1 { fname=pre c++; print header > fname}
{print > fname}' tokyo_medal.tsv
$ head awk_part_*
==> awk_part_0 <==
Rank Country Gold Silver Bronze Total
1 United States 39 41 33 113
2 China 38 32 18 88
3 Japan 27 14 17 58
==> awk_part_1 <==
Rank Country Gold Silver Bronze Total
4 Great Britain 22 21 22 65
5 ROC 20 28 23 71
6 Australia 17 7 22 46
==> awk_part_2 <==
Rank Country Gold Silver Bronze Total
7 Netherlands 10 12 14 36
8 France 10 12 11 33
9 Germany 10 11 16 37
==> awk_part_3 <==
Rank Country Gold Silver Bronze Total
10 Italy 10 10 20 40
如上面的输出所示,输入文件已按我们预期的方式与标题行分开。
5.2. awk命令的工作原理
现在,让我们通过awk命令并了解它是如何工作的:
- awk -v lines=”3″ -v pre=”awk_part_” – 首先,我们声明了两个 awk变量来定义每个拆分文件中有多少条记录以及文件名的前缀
- NR==1 { header=$0; next} – 当 awk读取第一行时,它将该行存储在 标题变量中并停止进一步处理
- (NR-1) % lines ==1 { fname=pre c++; print header > fname} – 当当前行是主干的第一条记录时,我们需要通过递增计数器 ( c ) 来更新文件名 ( fname ) 。此外,由于这将是一个新文件,我们将标头变量的值作为第一行添加到文件中
- {print > fname}’ tokyo_medal.tsv – 然后,我们可以将每个记录行重定向到当前的fname文件
这样,awk只读取一次输入文件就解决了问题。