跳至主要内容

数据科学中的 8 个实用 Shell 命令

数据科学家几乎每天都会用到哪些 shell 命令?在本教程中了解并学会使用它们!
· 12分钟 阅读

使用 AI 探索

在很多情况下,您需要快速分析、修改并处理数量多、体量大的文件。然而,这些文件往往是以逗号分隔值(CSV)的文本文件。用 Excel、LibreOffice 或 OpenOffice 等常见电子表格程序打开此类文件会占满机器内存。而且,将大量文件放到一个批次里处理,往往会因为某个意外的文件异常而在运行数小时后失败。

于是您难免要在卡死的界面、重启和漫长等待中耗费大量时间。

不过,其中大多数任务只需几行代码就能完成。而且,只要熟悉几条简单的 shell 命令行,就能在节约时间、减少挫败感方面发挥巨大作用。

本文将概述一些我几乎每天都会用到的 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。

查看我们的教程:在您的电脑上搭建数据科学环境。

主题