今天介绍一本非常不错的书,英文名是 Data Science at the Command Line。
书中介绍了如何用命令行来获取、处理和展示数据,效率远远高于自己开发 Python 脚本来实现这些功能,所以非常值得学习。
1. Linux 命令行介绍
Linux 系统有许多命令行工具,这些工具可以完成非常强大的功能。如 grep 命令可以用来在文本中搜索特定的字符串,wc 可以计算文本的行数,sort 可以对文本进行排序等。命令行工具运行的时候是一个进程,每个进程都有三个标准的输入输出流,分别是标准输入stdin, 标准输出stdout, 以及错误输出stderr。当在终端中运行 rev 命令时,rev 将接收用户在键盘上输入的字符串,进行反序后输出到屏幕,其原理如下图所示。
Figure 1.1: Every tool has three standard streams: standard input (stdin), standard output (stdout), and standard error (stderr)
我们还可以通过管道符号|将多个命令串联起来执行,前面一个命令的标准输出结果会传给后一个命令的标准输入进行进一步的处理。下面的命令下载一个文本文件,并通过 grep 搜索出其中含有 CHAPTER 的行:
 $ curl -s "https://www.gutenberg.org/files/11/11-0.txt" | grep " CHAPTER" CHAPTER I.     Down the Rabbit-Hole CHAPTER II.    The Pool of Tears CHAPTER III.   A Caucus-Race and a Long Tale CHAPTER IV.    The Rabbit Sends in a Little Bill CHAPTER V.     Advice from a Caterpillar CHAPTER VI.    Pig and Pepper CHAPTER VII.   A Mad Tea-Party CHAPTER VIII.  The Queen’s Croquet-Ground CHAPTER IX.    The Mock Turtle’s Story CHAPTER X.     The Lobster Quadrille CHAPTER XI.    Who Stole the Tarts? CHAPTER XII.   Alice’s Evidence
       复制代码
 这个命令行的工作原理如下图所示,curl 下载文件的内容通过标准输出成为 grep 的输入,grep 执行查找后输出到屏幕上:
Figure 1.2: The output from a tool can be piped to another tool
注意,如果 curl 执行出错,错误信息并不会传给 grep,而是显示在屏幕上。
我们还可以将命令行的输出进行重定向,比如重定向到文件中。下面的命令将执行结果重定向到 chapter.txt 文件中:
 $ curl "https://www.gutenberg.org/files/11/11-0.txt" | grep " CHAPTER" > chapters.txt  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                 Dload  Upload   Total   Spent    Left  Speed100  170k  100  170k    0     0   197k      0 --:--:-- --:--:-- --:--:--  198k $ cat chapters.txt CHAPTER I.     Down the Rabbit-Hole CHAPTER II.    The Pool of Tears CHAPTER III.   A Caucus-Race and a Long Tale CHAPTER IV.    The Rabbit Sends in a Little Bill CHAPTER V.     Advice from a Caterpillar CHAPTER VI.    Pig and Pepper CHAPTER VII.   A Mad Tea-Party CHAPTER VIII.  The Queen’s Croquet-Ground CHAPTER IX.    The Mock Turtle’s Story CHAPTER X.     The Lobster Quadrille CHAPTER XI.    Who Stole the Tarts? CHAPTER XII.   Alice’s Evidence
       复制代码
 下图是上面这个命令行的工作原理:
Figure 1.3: The output from a tool can be redirected to a file
在 Linux 系统中有一个特殊的文件/dev/null,我们可以通过将错误输出重定向到该文件的方式禁止命令在屏幕上显示错误信息,如下所示:
 $ cat movies.txt 404.txtMatrixStar WarsHome AloneIndiana JonesBack to the Future/usr/bin/cat: 404.txt: No such file or directory
       复制代码
 
 $ cat movies.txt 404.txt 2> /dev/null ➊MatrixStar WarsHome AloneIndiana JonesBack to the Future
       复制代码
 其中数字 2 代表的是标准错误输出 stderr。
2. 开发环境准备
该书的作者提供了一个 Docker 镜像用于开发者学习使用。Docker 的安装可以参考网上的教程,这里不再赘述。安装完 Docker 后,首先下载作者的镜像:
 $ docker pull datasciencetoolbox/dsatcl2e
       复制代码
 运行镜像:
 $ docker run --rm -it datasciencetoolbox/dsatcl2e
       复制代码
 Docker 容器相当于一个沙箱,我们没有办法直接访问里面的文件,容器内也无法访问外部的文件,但是我们可以将本地的一个目录映射到容器内部,这样就可以在容器内部访问本地的文件。通过在运行容器的时候指定-v 参数即可实现该功能,下面的命令将当前执行命令的目录映射到 docker 容器到根目录/data 下面:
 $ docker run --rm -it -v "$(pwd)":/data datasciencetoolbox/dsatcl2e
       复制代码
 这样我们在容器内部就可以在/data 目录下对外部的数据进行操作。
作者还提供了用于训练的测试数据,下载地址是:
https://www.datascienceatthecommandline.com/2e/data.zip
3. 获取数据
我们可以直接从网上下载数据,然后根据上一节的方法映在 docker 容器中进行处理,也可以在 docker 中直接从网上下载数据。Linux 系统中有一个非常强大的工具 curl,支持超过 20 多种网络协议,可以用来执行各种网络请求。
下载并保存文件
 $ curl -s "https://en.wikipedia.org/wiki/List_of_windmills_in_Friesland" -O
       复制代码
 或者
 $ curl -s "https://en.wikipedia.org/wiki/List_of_windmills_in_Friesland" > friesland.html
       复制代码
 -s 的作用是抑制输出错误信息,-O 将文件保存到本地。
Http 请求重定向
 $ curl -s "https://youtu.be/dQw4w9WgXcQ"
       复制代码
 
-L 参数用于指示 curl 处理需要重定向到网址。
 $ curl -sI "https://youtu.be/dQw4w9WgXcQ" | trim
       复制代码
 -I 参数指示 curl 只返回 http 请求到 header 数据。
文件解压缩
如果只想看看压缩文件里有哪些内容,使用-t 参数
 $ tar -tzf logs.tar.gz | trim
       复制代码
 -C 参数指定解压缩到特定目录下
 $ mkdir logs $ tar -xzf logs.tar.gz -C logs
       复制代码
 作者提供了一个简易的命令 unpack,
Excel 处理
CSVKit 提供了一系列工具,如 in2csv、csvgrep、csvlook 等来处理 csv 文件。
in2csv 这个命令可以将 Excel 文件转为 csv 文件
 $ in2csv top2000.xlsx | tee top2000.csv | trim
       复制代码
 要查看 csv 文件的内容,可以用下面的工具
 $ csvlook tmnt-with-header.csv│ name         │ nickname      │ mask_color │ weapon           │├──────────────┼───────────────┼────────────┼──────────────────┤│ Leonardo     │ Leo           │ blue       │ two ninjakens    ││ Raphael      │ Raph          │ red        │ pair of sai      ││ Michelangelo │ Mikey or Mike │ orange     │ pair of nunchaku ││ Donatello    │ Donnie or Don │ purple     │ staff            │ 
       复制代码
 这个工具将在终端中以格式化的方式显示结果。
搜索 csv 文件中特定的字符串:
 $ csvgrep top2000.csv --columns ARTIEST --regex '^Queen$' | csvlook -I ➊│ NR.  │ ARTIEST │ TITEL                           │ JAAR │├──────┼─────────┼─────────────────────────────────┼──────┤│ 2    │ Queen   │ Bohemian Rhapsody               │ 1975 ││ 11   │ Queen   │ Love Of My Life                 │ 1975 ││ 46   │ Queen   │ Innuendo                        │ 1991 ││ 55   │ Queen   │ Don't Stop Me Now               │ 1979 ││ 70   │ Queen   │ Somebody To Love                │ 1976 ││ 85   │ Queen   │ Who Wants To Live Forever       │ 1986 ││ 89   │ Queen   │ The Show Must Go On             │ 1991 ││ 131  │ Queen   │ Killer Queen                    │ 1974 │… with 24 more lines
       复制代码
 --regex 指定一个正则表达式。
数据库
在命令行中处理数据库等工具有很多,其中 sql2csv,也是 CSVkit 中的一个工具,可以处理包括 Oracle、MySQL 等各种数据库,
 $ sql2csv --db 'sqlite:///r-datasets.db' \> --query 'SELECT row_names AS car, mpg FROM mtcars ORDER BY mpg' | csvlook│ car                 │  mpg │├─────────────────────┼──────┤│ Cadillac Fleetwood  │ 10.4 ││ Lincoln Continental │ 10.4 ││ Camaro Z28          │ 13.3 ││ Duster 360          │ 14.3 ││ Chrysler Imperial   │ 14.7 ││ Maserati Bora       │ 15.0 ││ Merc 450SLC         │ 15.2 ││ AMC Javelin         │ 15.2 │… with 24 more lines
       复制代码
 Web API 数据
这里也是用 curl 请求 api 数据
 $ curl -s "https://anapioficeandfire.com/api/characters/583" | jq '.'{"url": "https://anapioficeandfire.com/api/characters/583","name": "Jon Snow","gender": "Male","culture": "Northmen","born": "In 283 AC","died": "", ➊"titles": ["Lord Commander of the Night's Watch"  ],
       复制代码
 curl 也支持需要认证的 http 请求,可以在 header 中传入 token 等认证参数,这里将 apikey 保存到文件中,传给 curl 进行认证请求:
 $ curl -s "http://newsapi.org/v2/everything?q=linux&apiKey=$(< /data/.secret/newsapi.org_apikey)" |> jq '.' | trim 30{  "status": "ok",  "totalResults": 9088,  "articles": [    {
       复制代码
 对数据流进行采样
 $ curl -s "https://stream.wikimedia.org/v2/stream/recentchange" |> sample -s 10 > wikimedia-stream-sample
       复制代码
 
4. 创建复杂的脚本
各种命令组合在一起可以完成非常复杂的任务,如下
 $ curl -sL "https://www.gutenberg.org/files/11/11-0.txt" | ➊> tr '[:upper:]' '[:lower:]' | ➋> grep -oE "[a-z\']{2,}" | ➌> sort | ➍> uniq -c | ➎> sort -nr | ➏> head -n 10 ➐   1839 the    942 and    811 to    638 of    610 it    553 she    486 you    462 said    435 in    403 alice
       复制代码
 ➊ curl下载一本电子书.➋ tr将所有的文本转为小写.➌ grep提取出所有的单词,每个单词放到单独的一行中.➍ sort按字母顺序排序.➎ uniq删除重复的词,并计算每个词出现的频率.➏ sort按出现频率降序排序➐ head保留前 10 个结果
上面这些词在英文中被称为 stopwords,它们出现的频率本身就很高,为了更好的统计单词,最好过滤掉这些词不予统计。先通过下面的命令找出所有的 stopwords:
 $ curl -sL "https://raw.githubusercontent.com/stopwords-iso/stopwords-en/master/stopwords-en.txt" |> sort | tee stopwords | trim 201039aableableaboutaboutaboveabroadabstaccordanceaccordingaccordinglyacrossactactuallyadaddedadjadoptedae… with 1278 more lines
       复制代码
 再使用下面的命令,统计除 stopwords 之外的单词:
 $ curl -sL "https://www.gutenberg.org/files/11/11-0.txt" |> tr '[:upper:]' '[:lower:]' |> grep -oE "[a-z\']{2,}" |> sort |> grep -Fvwf stopwords | ➊> uniq -c |> sort -nr |> head -n 10    403 alice     98 gutenberg     88 project     76 queen     71 time     63 king     60 turtle     57 mock     56 hatter     55 gryphon
       复制代码
 我们可以将上面的命令封装到一个脚本文件中,如下所示
 $ bat top-words-5.sh───────┬────────────────────────────────────────────────────────────────────────       │ File: top-words-5.sh───────┼────────────────────────────────────────────────────────────────────────   1   │ #!/usr/bin/env bash   2   │   3   │ NUM_WORDS="${1:-10}"   4   │   5   │ tr '[:upper:]' '[:lower:]' |   6   │ grep -oE "[a-z\']{2,}" |   7   │ sort |   8   │ grep -Fvwf stopwords |   9   │ uniq -c |  10   │ sort -nr |  11   │ head -n "${NUM_WORDS}"───────┴────────────────────────────────────────────────────────────────────────
       复制代码
 NUM_WORDS 是从命令行传给脚本的参数, $1 表示命令行的第一个参数,如果没有传参数取默认值“10”。
 $ curl -sL "https://www.gutenberg.org/files/11/11-0.txt" > alice.txt $ < alice.txt ./top-words-5.sh 20    403 alice     98 gutenberg     88 project     76 queen     71 time
       复制代码
 (待续)
评论