在很多情况下,您需要快速分析、修改并处理数量多、体量大的文件。然而,这些文件往往是以逗号分隔值(CSV)的文本文件。用 Excel、LibreOffice 或 OpenOffice 等常见电子表格程序打开此类文件会占满机器内存。而且,将大量文件放到一个批次里处理,往往会因为某个意外的文件异常而在运行数小时后失败。
于是您难免要在卡死的界面、重启和漫长等待中耗费大量时间。
不过,其中大多数任务只需几行代码就能完成。而且,只要熟悉几条简单的 shell 命令行,就能在节约时间、减少挫败感方面发挥巨大作用。
本文将概述一些我几乎每天都会用到的 shell 命令。希望对您有所帮助。
速记
请注意 ,shell 有不同类型 (bash、zsh 等),其中 bash 最常见,因为它是 OS X 以及主流 Linux 发行版的默认 shell。Zsh (及 Oh My Zsh )是一个流行且强大的替代选择。本文中的 shell 命令已在 OS X(macOS)上的 bash 测试过,应该也适用于其他 shell 和环境。
以下所有示例均基于 UCI 机器学习库中的 adult 数据集 ,也称为“人口普查收入”数据集,可在此处 获取。该数据集通常用于基于人口普查数据预测收入是否超过 \$50K/年。它包含 48842 行和 14 个属性,算不上大,但足以用于演示示例。尽管数据文件扩展名为 .data,但它是格式良好的 CSV 文件。欢迎下载数据集跟着练习!
用 wc 计数
拿到一个新的文本文件,您可能想知道它包含多少行。可以使用词数统计命令 wc -l 来完成:
$ wc -l adult.data
32562 adult.data技术支持
这表示 adult.data 文件包含 32562 行。-l 标志让 wc 统计行数。您也可以用 -w 标志让 wc 改为统计词数。
$ wc -w adult.data
488415 adult.data技术支持
adult.data 文件包含将近 50 万个词。
wc 还可以通过将简单的 ls -l 命令输出作为 wc 的输入,统计目录中的文件数量。使用管道符号 | 将一个命令的输出作为另一个命令的输入,这是一种称为管道 的常用 shell 模式。本文中有多个示例会用到它。
假设您现在有一个包含许多文件的文件夹。以下命令会准确告诉您它包含多少个文件:
$ ls -l < folder> | wc -l
508 技术支持
如果配合通配符和子文件夹,wc 还有许多其他用法。
回到您的 adult.data 文件,用 head 命令查看前几行。默认情况下,head 输出 10 行,您可以使用 -n 标志将输出限制为前 2 行。
$ head -n 2 adult.data
39 , State-gov, 77516 , Bachelors, 13 , Never-married, Adm-clerical, Not-in-family, White, Male, 2174 , 0 , 40 , United-States, <= 50K
50 , Self-emp-not-inc, 83311 , Bachelors, 13 , Married-civ-spouse, Exec-managerial, Husband, White, Male, 0 , 0 , 13 , United-States, <= 50K技术支持
您会注意到文件没有表头行。列名不在文件里。您可以使用 cat 命令将两个文件连接起来,从而添加那行表头。
用 cat 连接文件
cat 命令会将文件内容打印到标准输出(即终端)。它也可用于连接一系列文件。命令 cat file_1.csv file_2.csv > target_file.csv 会将 file_1.csv 与 file_2.csv 的内容合并到 target_file.csv 中,把 file_2.csv 追加到 file_1.csv 之后。
原始数据集中没有表头文件,您需要创建它。为此,您可以将以逗号分隔的列名列表通过 echo 写入 header.csv 文件。
$ echo "age,workclass,fnlwgt,education,education-num,marital-status,occupation,relationship,race,sex,capital-gain,capital-loss,native-country,class" > header.csv技术支持
在这个示例中,您使用了 shell 重定向字符 > 将 cat 的输出写入 adult.csv 文件。> 会在文件不存在时创建文件,或在文件已存在时完全替换其内容。使用双重符号 >> 则会在不清空已有内容的情况下将新内容追加到已存在的文件。
现在把 header.csv 文件加到 adult.data 文件开头。同时,由于它本质上是个 CSV 格式文件,您可以把 adult.data 重命名为 adult.csv。
$ cat header. csv adult. data > adult. csv
技术支持
用以下命令检查 adult.csv 的第一行是否包含列名:
$ head -n 1 adult.csv
age,workclass,fnlwgt,education,education-num,marital-status,occupation,relationship,race,sex,capital-gain,capital-loss,native-country,class
39 , State-gov, 77516 , Bachelors, 13 , Never-married, Adm-clerical, Not-in-family, White, Male, 2174 , 0 , 40 , United-States, <= 50K技术支持
用 sed 修改文件
另一种常见的数据挖掘场景是文件损坏或格式不当,比如包含非 UTF-8 字符或逗号位置错误。您可以不用打开文件,直接使用 sed 命令来修正它。
通用的 sed 模式为
$ sed "s/<string to replace>/<string to replace it with>/g" < source_file> > < target_file> .技术支持
adult.csv 数据集使用 ? 字符来表示缺失值。您可以用 sed 将该字符替换为更合适的默认值。空字符串更适合作为缺失值的标记,因为在将数据加载 到 Pandas DataFrame 时,它会被解释为 NaN。
$ grep ", ?," adult.csv | wc -l
2399 技术支持
这表示至少有一列以 ? 表示缺失值的行共有 2399 行。下面的命令会将所有包含 ? 的列替换为空字符串。所有只包含 ?、后跟空格的单元格现在都会变成真正的空值。
$ sed "s/, ?,/,,/g" adult.csv > adult.csv技术支持
请注意,在源字符串和目标字符串中使用了列分隔符 ,,以避免替换数据集中其他位置的合法问号。
从大型文件中抽取子集
设想您正在处理一个拥有 3000 多万行的文件。在编写 Python 或 R 脚本时,您会希望只在该大文件的一个样本上测试工作流程,而不是将整个数据集都加载到内存中。您可以结合使用 head 和 tail 来创建原始数据集的一个子样本。
head 输出文件的开头部分;
tail 输出文件的末尾部分
如前所述,这些命令接受 -n 标志(即 --lines),用来限制输出的行数。tail 还可以使用 -f 标志(f 即 --follow),当文件末尾新增行时,持续输出追加的数据。这在您的脚本运行时通过 tail -f <logfile> 监控日志文件尤其方便。
将管道符号 | 与 head、tail 混合使用,您可以从源文件中抽取一定数量的行,并将内容导出到一个子集文件。例如,要从第 100 行开始提取 20 行:
$ head -n 120 adult.csv | tail -n 20 > adult_sample.csv技术支持
请注意,您需要先用 head 取到起始行 + 行数 ,再用 tail 取行数 。通用的子集命令为:
$ head - n < total_lines> < source_file> | tail - n < number_of_lines> > < target_file>
技术支持
其中 total_lines = first_line + number_of_lines。不过,新的样本文件不再包含表头行。我们已经知道该如何把表头加回子集文件了:使用 cat 将子集文件与之前创建的 header.csv 拼接:
$ cat adult_sample. csv header. csv > adult_sample_with_header. csv
技术支持
请注意,作为目标文件时您没有继续使用源文件名 adult\_sample.csv,而是新建了 adult\_sample\_with\_header.csv。在使用 cat 时,如果目标文件与某个源文件同名,可能会导致意外的文件内容。更好的做法是为 cat 命令的输出创建一个新文件。
用 uniq 查找重复项
使用 uniq 命令可以在文件中查找相邻的重复行。uniq 有多个标志,其中较常用的是:
uniq -c:在每行前添加重复计数;
uniq -d:只输出重复行;以及
uniq -u:只输出唯一行。
不过,uniq 并不智能。如果重复行不相邻,它就检测不到。这意味着您需要先对文件进行 sort。以下命令统计 adult.csv 中重复行的数量。
$ sort adult.csv | uniq -d | wc -l
23 技术支持
结果显示有 23 个重复项。下面这条命令将所有行的输出与重复计数一起取出,按逆序排序,并输出出现次数最多的前 3 个重复项:
$ sort adult.csv | uniq -c | sort -r | head -n 3
3 25 , Private, 195994 , 1st-4th, 2 , Never-married, .. .
2 90 , Private, 52386 , Some-college, 10 , Never-married, .. .
2 49 , Self-emp-not-inc, 43479 , Some-college, 10 , Married-civ-spouse, .. .技术支持
通过将 sort 与 uniq 及不同标志组合,可以获得许多强大的功能。使用 man sort 和 man uniq 进一步探索这些命令。
用 cut 选择列
CSV 文件与 shell 命令的妙处在于,您还可以使用 cut 在列级别进行操作,选择特定列。cut 主要有两个标志:-d 用于指定列分隔符,-f 用于指定要处理的列。下面的示例中,您用 cut 来统计分类变量 workclass(第 2 列)取值的唯一数量。
先选择 workclass 列,并通过管道传给 head 以确认选对了列:
$ cut -d "," -f 2 adult.csv | head -3
workclass
State-gov
Self-emp-not-inc技术支持
现在要统计唯一值,您需要对 cut 的输出排序,并将结果通过管道传给 uniq -c,如下所示:
$ cut -d "," -f 2 adult.csv | sort | uniq -c
1837
960 Federal-gov
2093 Local-gov
7 Never-worked
22696 Private
1116 Self-emp-inc
2541 Self-emp-not-inc
1298 State-gov
14 Without-pay
1 workclass技术支持
这告诉您,例如有 1837 个空值,而出现次数最多的类别远远是 Private,共有 22969 次。
上述命令行与将 value_counts() 方法应用于包含 adult.csv 数据的 DataFrame 类似。
循环
到目前为止,您主要是在处理一个文件。要重命名、处理或传输大量文件,您应当把循环加入到 shell 工具箱中。bash shell 中循环的一般格式为:
while true ; do
_do something_ ;
done 技术支持
试着用起来。假设您有 1000 个文件,其文件名中包含空格,想把每个空格都替换为下划线“_”。
replace_source = ' '
replace_target = '_'
for filename in ./*.csv; do
new_filename = ${filename/ / $replace_source/ $replace_target}
mv "$filename " "$new_filename "
done 技术支持
在这个示例中,
您首先声明了两个变量:replace_source 和 replace_target,分别代表空格和下划线字符
您遍历当前文件夹中的所有 *.csv 文件
对每个 filename,通过将每个空格替换为下划线来创建一个 new_filename
并使用 mv 命令将文件从当前文件名重命名为新文件名
实际上,您不仅使用了 shell 来遍历目录中的文件,还创建了变量。
变量
本次 shell 命令入门将以变量为结尾。在 shell 中创建变量很简单:
$ varname = '<a string>'
$ varname = a number技术支持
例如:
$ varname = 'Hello world'
$ varname = 123.4 技术支持
请注意,等号两侧不能有空格。var = '<a string>' 这样的写法无法工作。要返回变量值,只需 echo 它:
$ echo $varname
123.4 技术支持
而在脚本中使用它时,如前所示将其放在引号内:
$ mv "$filename " "$new_filename " 技术支持
编写循环并使用变量,为更复杂的文件操作打开了大门,也是通往 shell 脚本的入口,这超出了本文的入门范围。不过,只要从小处开始并伴随信心提升逐步改进,编写 shell 脚本其实并不难。
结语
对于数据科学家的日常工作而言,shell 命令行极其有用。还有许多示例和用例值得探索。正如您将看到的,利用丰富的 shell 命令及其相关标志,往往总有不同的方法 可以实现某个目标。在这种情况下,保持简单始终是制胜之道。
希望这里的示例能帮助您把 shell 命令融入数据处理工具箱。如有需要,欢迎通过 Twitter 与我交流并分享您的 shell 技巧:@alexip 。
查看我们的教程:在您的电脑上搭建数据科学环境 。
在很多情况下,您需要快速分析、修改并处理数量多、体量大的文件。然而,这些文件往往是以逗号分隔值(CSV)的文本文件。用 Excel、LibreOffice 或 OpenOffice 等常见电子表格程序打开此类文件会占满机器内存。而且,将大量文件放到一个批次里处理,往往会因为某个意外的文件异常而在运行数小时后失败。
于是您难免要在卡死的界面、重启和漫长等待中耗费大量时间。
不过,其中大多数任务只需几行代码就能完成。而且,只要熟悉几条简单的 shell 命令行,就能在节约时间、减少挫败感方面发挥巨大作用。
本文将概述一些我几乎每天都会用到的 shell 命令。希望对您有所帮助。
速记
请注意,shell 有不同类型(bash、zsh 等),其中 bash 最常见,因为它是 OS X 以及主流 Linux 发行版的默认 shell。Zsh(及 Oh My Zsh)是一个流行且强大的替代选择。本文中的 shell 命令已在 OS X(macOS)上的 bash 测试过,应该也适用于其他 shell 和环境。
以下所有示例均基于 UCI 机器学习库中的 adult 数据集,也称为“人口普查收入”数据集,可在此处获取。该数据集通常用于基于人口普查数据预测收入是否超过 \$50K/年。它包含 48842 行和 14 个属性,算不上大,但足以用于演示示例。尽管数据文件扩展名为
.data,但它是格式良好的 CSV 文件。欢迎下载数据集跟着练习!用
wc计数拿到一个新的文本文件,您可能想知道它包含多少行。可以使用词数统计命令
wc -l来完成:这表示
adult.data文件包含 32562 行。-l标志让wc统计行数。您也可以用-w标志让wc改为统计词数。adult.data文件包含将近 50 万个词。wc还可以通过将简单的ls -l命令输出作为wc的输入,统计目录中的文件数量。使用管道符号|将一个命令的输出作为另一个命令的输入,这是一种称为管道的常用 shell 模式。本文中有多个示例会用到它。假设您现在有一个包含许多文件的文件夹。以下命令会准确告诉您它包含多少个文件:
如果配合通配符和子文件夹,
wc还有许多其他用法。回到您的
adult.data文件,用head命令查看前几行。默认情况下,head输出 10 行,您可以使用-n标志将输出限制为前 2 行。您会注意到文件没有表头行。列名不在文件里。您可以使用
cat命令将两个文件连接起来,从而添加那行表头。用
cat连接文件cat命令会将文件内容打印到标准输出(即终端)。它也可用于连接一系列文件。命令cat file_1.csv file_2.csv > target_file.csv会将file_1.csv与file_2.csv的内容合并到target_file.csv中,把file_2.csv追加到file_1.csv之后。原始数据集中没有表头文件,您需要创建它。为此,您可以将以逗号分隔的列名列表通过
echo写入header.csv文件。在这个示例中,您使用了 shell 重定向字符
>将cat的输出写入adult.csv文件。>会在文件不存在时创建文件,或在文件已存在时完全替换其内容。使用双重符号>>则会在不清空已有内容的情况下将新内容追加到已存在的文件。现在把
header.csv文件加到adult.data文件开头。同时,由于它本质上是个 CSV 格式文件,您可以把adult.data重命名为adult.csv。用以下命令检查
adult.csv的第一行是否包含列名:用
sed修改文件另一种常见的数据挖掘场景是文件损坏或格式不当,比如包含非 UTF-8 字符或逗号位置错误。您可以不用打开文件,直接使用
sed命令来修正它。通用的
sed模式为adult.csv数据集使用?字符来表示缺失值。您可以用sed将该字符替换为更合适的默认值。空字符串更适合作为缺失值的标记,因为在将数据加载到 Pandas DataFrame 时,它会被解释为NaN。这表示至少有一列以
?表示缺失值的行共有 2399 行。下面的命令会将所有包含?的列替换为空字符串。所有只包含?、后跟空格的单元格现在都会变成真正的空值。请注意,在源字符串和目标字符串中使用了列分隔符
,,以避免替换数据集中其他位置的合法问号。从大型文件中抽取子集
设想您正在处理一个拥有 3000 多万行的文件。在编写 Python 或 R 脚本时,您会希望只在该大文件的一个样本上测试工作流程,而不是将整个数据集都加载到内存中。您可以结合使用
head和tail来创建原始数据集的一个子样本。head输出文件的开头部分;tail输出文件的末尾部分如前所述,这些命令接受
-n标志(即--lines),用来限制输出的行数。tail还可以使用-f标志(f即--follow),当文件末尾新增行时,持续输出追加的数据。这在您的脚本运行时通过tail -f <logfile>监控日志文件尤其方便。将管道符号
|与head、tail混合使用,您可以从源文件中抽取一定数量的行,并将内容导出到一个子集文件。例如,要从第 100 行开始提取 20 行:请注意,您需要先用
head取到起始行 + 行数,再用tail取行数。通用的子集命令为:其中
total_lines = first_line + number_of_lines。不过,新的样本文件不再包含表头行。我们已经知道该如何把表头加回子集文件了:使用cat将子集文件与之前创建的header.csv拼接:请注意,作为目标文件时您没有继续使用源文件名
adult\_sample.csv,而是新建了adult\_sample\_with\_header.csv。在使用cat时,如果目标文件与某个源文件同名,可能会导致意外的文件内容。更好的做法是为cat命令的输出创建一个新文件。用
uniq查找重复项使用
uniq命令可以在文件中查找相邻的重复行。uniq有多个标志,其中较常用的是:uniq -c:在每行前添加重复计数;uniq -d:只输出重复行;以及uniq -u:只输出唯一行。不过,
uniq并不智能。如果重复行不相邻,它就检测不到。这意味着您需要先对文件进行sort。以下命令统计adult.csv中重复行的数量。结果显示有 23 个重复项。下面这条命令将所有行的输出与重复计数一起取出,按逆序排序,并输出出现次数最多的前 3 个重复项:
通过将
sort与uniq及不同标志组合,可以获得许多强大的功能。使用man sort和man uniq进一步探索这些命令。用
cut选择列CSV 文件与 shell 命令的妙处在于,您还可以使用
cut在列级别进行操作,选择特定列。cut主要有两个标志:-d用于指定列分隔符,-f用于指定要处理的列。下面的示例中,您用cut来统计分类变量workclass(第 2 列)取值的唯一数量。先选择
workclass列,并通过管道传给 head 以确认选对了列:现在要统计唯一值,您需要对 cut 的输出排序,并将结果通过管道传给
uniq -c,如下所示:这告诉您,例如有 1837 个空值,而出现次数最多的类别远远是
Private,共有 22969 次。上述命令行与将
value_counts()方法应用于包含adult.csv数据的 DataFrame 类似。循环
到目前为止,您主要是在处理一个文件。要重命名、处理或传输大量文件,您应当把循环加入到 shell 工具箱中。bash shell 中循环的一般格式为:
试着用起来。假设您有 1000 个文件,其文件名中包含空格,想把每个空格都替换为下划线“_”。
在这个示例中,
replace_source和replace_target,分别代表空格和下划线字符*.csv文件filename,通过将每个空格替换为下划线来创建一个new_filenamemv命令将文件从当前文件名重命名为新文件名实际上,您不仅使用了 shell 来遍历目录中的文件,还创建了变量。
变量
本次 shell 命令入门将以变量为结尾。在 shell 中创建变量很简单:
例如:
请注意,等号两侧不能有空格。
var = '<a string>'这样的写法无法工作。要返回变量值,只需echo它:而在脚本中使用它时,如前所示将其放在引号内:
编写循环并使用变量,为更复杂的文件操作打开了大门,也是通往 shell 脚本的入口,这超出了本文的入门范围。不过,只要从小处开始并伴随信心提升逐步改进,编写 shell 脚本其实并不难。
结语
对于数据科学家的日常工作而言,shell 命令行极其有用。还有许多示例和用例值得探索。正如您将看到的,利用丰富的 shell 命令及其相关标志,往往总有不同的方法可以实现某个目标。在这种情况下,保持简单始终是制胜之道。
希望这里的示例能帮助您把 shell 命令融入数据处理工具箱。如有需要,欢迎通过 Twitter 与我交流并分享您的 shell 技巧:@alexip。
查看我们的教程:在您的电脑上搭建数据科学环境。