沒想到 Shell 命令竟然還能這么玩?
正文開始
在前面的這篇文章中 —— 優(yōu)秀的程序員是如何利用工具來提升工作效率的?,石頭介紹了可以提高程序猿工作效率的一些軟件和工具及相關(guān)配置。文中提到了, 程序猿應(yīng)該了解一些常見的命令行工具來提高效率。
本文是一個(gè)命令行工具的綜合應(yīng)用,將用一個(gè)具體的例子來闡述如何用 Shell 來進(jìn)行高效地?cái)?shù)據(jù)統(tǒng)計(jì)和分析。最近北京又開始了新一批積分落戶的填報(bào)工作,恰好這篇文章用 shell 來對首批北京積分落戶同學(xué)進(jìn)行 "大數(shù)據(jù)"分析。
現(xiàn)如今到處都是各種"大數(shù)據(jù)",本文分析對象也就是首批積分落戶的6000多條數(shù)據(jù)而已,顯然不能算什么大數(shù)據(jù)。
印象中,我記得當(dāng)初該官網(wǎng)的這6000多條數(shù)據(jù)也是一次性就能wget下來的(后端估計(jì)沒做限制,可能稍微調(diào)整下接口的分頁參數(shù)之類不需要嚴(yán)格按照各種分頁多次下載)。(注:本文舊文重新整理發(fā)送。)
問題描述
輸入是 json數(shù)據(jù),格式化之后的 json 數(shù)據(jù)主題結(jié)構(gòu)如下所示,rows為數(shù)組,數(shù)組中元素所代表的 object 即描述了獲得北京戶口的同學(xué)的各種屬性:例如分?jǐn)?shù)、排名、身份證號(后四位打碼了)、公司等等信息。為了方便大家練習(xí)對數(shù)據(jù)進(jìn)行試驗(yàn),我將文中的數(shù)據(jù)附在這里(https://www.tanglei.name/resources/use-shell-to-analysis-the-first-people-of-getting-residence-of-beijing-by-score/jifenluohu.json.gz)。
- "rows": [
- {
- "id": 62981,
- "idCard": "32092219721222****",
- "idCardSHA": "9ef70bde894959a4e4a1d1b2b9592b470294f9e4012a8cf480319665d1a7c1c6",
- "insertTime": 1539518353000,
- "integralQualified": 1,
- "internetAnnual": {
- "annual": 2018,
- "id": 43,
- "insertTime": 1539518353000,
- "publicityEnd": 1540224000000,
- "publicityStart": 1539591600000,
- "publishResultEndDate": 1541679300000,
- "publishResultStartDate": 1539591600000,
- "publishResultStatus": 1,
- "score": 90.75,
- "status": 1
- },
- "md5Code": "54e9ff7ce0b004f7141b157f8afc66db",
- "name": "楊效豐",
- "pxid": 1,
- "ranking": 1,
- "s1": 51,
- "s10": 0,
- "s2": 12.59,
- "s3": 15,
- "s4": 0,
- "s5": 4,
- "s6": 0,
- "s7": 20,
- "s8": 20,
- "s9": 0,
- "score": 122.59,
- "unit": "北京利德華福電氣技術(shù)有限公司"
- },
拿到這個(gè)文件,比如希望你用最快的方法獲得以下信息,你將會怎么做?
- 獲取取得戶口名額最多的top10公司
- 獲取取得戶口名額的人中姓氏最多的
- 獲取戶口名字中叫啥名最流行
- 獲取年齡分布
- 獲取取得戶口的同學(xué)戶籍地top10
- 生肖/星座/生日...
當(dāng)然,方法有很多,比如熟悉各種編程語言的,例如 python, php, java 等等寫個(gè)簡單的腳本程序,也能比較快獲取答案。或者把相應(yīng)的數(shù)據(jù)提取出來,放到 excel 中也可以。
如果你對 Shell 很熟悉,那真的是分分鐘,應(yīng)該是秒秒鐘就能獲取答案。就算用 Shell 來實(shí)現(xiàn),不同的人可能也有不同的寫法,后面我就列舉其中的一種來解決這些問題。
本文不對 Shell 具體每個(gè)命令做過多的解釋,不熟悉的同學(xué)可以直接 man $cmd 或者 $cmd --help 等等查看。
之前我也寫過一篇名叫 Shell 助力開發(fā)效率提升 的文章,算是給常用的命令的常用參數(shù)做了一個(gè)解釋和示例,有興趣的同學(xué)可以前往查閱。
問題解答
獲取取得戶口名額最多的top10公司
看看想通過積分落戶,最好是進(jìn)哪些公司,哈哈。
"unit": "北京利德華福電氣技術(shù)有限公司"
先通過 grep 得到包含公司名字的一行,然后通過 ":" 分割 cut 取第2列得到公司名字,對結(jié)果進(jìn)行sort排序進(jìn)行去重uniq統(tǒng)計(jì)得到重復(fù)次數(shù),次時(shí)結(jié)果為重復(fù)次數(shù) 公司名,再對第一列-k 1重復(fù)數(shù)字進(jìn)行按照數(shù)字排序逆序-nr 即 sort -nr -k 1,最后取結(jié)果的前10行 head -n 10。
- ➜ 積分落戶 > grep 'unit' jifenluohu.json| cut -f2 -d: | sort | uniq -c | sort -nr -k 1 | head -n 10
- 137 "北京華為數(shù)字技術(shù)有限公司"
- 73 "中央電視臺"
- 57 "北京首鋼建設(shè)集團(tuán)有限公司"
- 55 "百度在線網(wǎng)絡(luò)技術(shù)(北京)有限公司"
- 48 "聯(lián)想(北京)有限公司"
- 40 "北京外企人力資源服務(wù)有限公司"
- 40 "中國民生銀行股份有限公司"
- 39 "國際商業(yè)機(jī)器(中國)投資有限公司"
- 29 "中國國際技術(shù)智力合作有限公司"
- 27 "華為技術(shù)有限公司北京研究所"
獲取取得戶口名額的人中姓氏最多的
看看想通過積分落戶,最好是姓啥,哈哈。
"name": "楊效豐",
套路跟之前差不多的,我這邊就不特別指出了。
下面shell實(shí)際上是取到這行后,將真正表示名字之前的所有字符都刪除,就只剩下名字開頭了,取行首第一個(gè)字符cut -c 1即得到姓,再按照之前的套路就能拿到了。
其實(shí)用什么sed替換冗余的字符都是多余的,因?yàn)閖son的格式都是良好的,可以直接通過 cut -c ? 取姓這個(gè)字符即可。
也不用挨個(gè)去數(shù)到底是第幾個(gè)字符,直接 copy出來,然后 echo -n $paste | wc -c 就能數(shù)到第幾個(gè)字符了。
看結(jié)果還是姓 "張, 王" 之類的最有戲。??
- # 或者 grep '"name":' jifenluohu.json| sed 's|"name": "||g' | sed 's|[[:space:]]||g' | cut -c 1 | sort | uniq -c | sort -nr -k 1 | head -n 10
- ➜ 積分落戶 > grep '"name":' jifenluohu.json| sed 's|"name": "||g' | sed 's| ||g' | cut -c 1 | sort | uniq -c | sort -nr -k 1 | head -n 10
- 541 張
- 531 王
- 462 李
- 376 劉
- 205 陳
- 193 楊
- 166 趙
- 132 孫
- 95 郭
- 95 徐
獲取戶口名字中叫啥名最流行
套路差不多,不做過多解釋了。
- ➜ 積分落戶 > grep '"name":' jifenluohu.json| sed 's|"name": "||g' | sed 's|[[:space:]]||g' | cut -c 2-4 | sort | uniq -c | sort -nr -k 1 | head -n 10
- 51 偉",
- 39 靜",
- 38 濤",
- 36 勇",
- 36 軍",
- 32 敏",
- 31 穎",
- 30 鵬",
- 28 杰",
- 28 峰",
- # 取名字, 必須包含2個(gè)字
- ➜ 積分落戶 > grep '"name":' jifenluohu.json| sed 's|"name": "||g' | sed 's|[[:space:]]||g' | cut -c 2-3 | sed '/"/d' | sort | uniq -c | sort -nr -k 1 | head -n 10
- 19 海濤
- 19 曉東
- 12 志強(qiáng)
- 11 海燕
- 11 永強(qiáng)
- 11 建華
- 10 雪梅
- 9 海龍
- 9 麗娜
- 8 洪濤
作為碼農(nóng),必須得養(yǎng)成對自己得到結(jié)果進(jìn)行自測的習(xí)慣,所以如果對自己的結(jié)果不夠自信,可以正向去計(jì)算一下最終的結(jié)果。
例如可以簡單grep一下進(jìn)行驗(yàn)證,叫 "海濤" 的是不是19個(gè)。
- ➜ 積分落戶 > grep '海濤' jifenluohu.json | wc -l
- 19
獲取年齡分布
思路是截取身份證中號碼中代表出生年的4位數(shù),然后拿當(dāng)前年份2019減出生年得到年齡,后面的套路又一樣了。
bc 一個(gè)簡單的計(jì)算器程序,了解下?
- ➜ shell-train > echo "3+2-5/5" | bc
- 4
- ➜ shell-train > echo "3.141592*5-4" | bc
- 11.707960
- #思路1: `cut -c 9-12` 獲取出生年, 拼接表達(dá)式 `2019-出生年` 得到年齡.
- ➜ 積分落戶 > grep '"idCard":' jifenluohu.json| cut -f2 -d: | cut -c 9-12 | xargs -n1 echo 2019 -|bc | sort | uniq -c
- 3 34
- 13 35
- 39 36
- 109 37
- 162 38
- 302 39
- 507 40
- 773 41
- 799 42
- 813 43
- 757 44
- 586 45
- 507 46
- 378 47
- 238 48
- 4 49
- 9 50
- 1 51
- 4 52
- 3 53
- 2 54
- 5 55
- 1 56
- 1 58
- 1 59
- 1 60
- 1 61
awk 是個(gè)好東西, 多練練.
- # 拿到出生年后, 直接通過 awk 計(jì)算結(jié)果輸出
- ➜ 積分落戶 > grep '"idCard":' jifenluohu.json| cut -f2 -d: | cut -c 9-12 |awk '{print 2019-$1}' | sort | uniq -c
- 3 34
- 13 35
- 39 36
- 109 37
- 162 38
- 302 39
- 507 40
- 773 41
- 799 42
- 813 43
- 757 44
- 586 45
- 507 46
- 378 47
- 238 48
- 4 49
- 9 50
- 1 51
- 4 52
- 3 53
- 2 54
- 5 55
- 1 56
- 1 58
- 1 59
- 1 60
- 1 61
獲取取得戶口的同學(xué)戶籍地top10
有時(shí)候,我們在寫Shell的時(shí)候,為了debug方便,可能會將一些中間結(jié)果緩存到文件中,后續(xù)以該文件為基礎(chǔ)進(jìn)行后續(xù)的計(jì)算。
比如先拿到top10的身份證中代表的戶籍地的四位編碼,這里需要借助另外的一個(gè)表示身份證戶籍地的編碼來進(jìn)行對應(yīng)。
借此機(jī)會解釋下 join 這個(gè)命令。
- # 身份證前4位為例, 拿到戶籍地
- grep '"idCard":' jifenluohu.json| cut -f2 -d: | cut -c 3-6 | sort | uniq -c | sort -nr -k 1 >topcity.code
- # 城市列表
- ➜ 積分落戶 > more city.csv
- 11,北京市
- 1101,北京市市轄區(qū)
- 110101,北京市東城區(qū)
- 110102,北京市西城區(qū)
- 110103,北京市崇文區(qū)
- 110104,北京市宣武區(qū)
- 110105,北京市朝陽區(qū)
- # grep -E '^[0-9]{4},' city.csv | sed 's|,| |g' > city.code4
- ➜ shell-train > head -n 2 city.code4
- 1101 北京市市轄區(qū)
- 1102 北京市市轄縣
- ➜ shell-train > head -n 2 topcity.code
- 197 1201
- 156 1302
- ➜ shell-train > join
- usage: join [-a fileno | -v fileno ] [-e string] [-1 field] [-2 field]
- [-o list] [-t char] file1 file2
其實(shí),join 就類似sql中的 ...inner join ...on ..., -t 分隔符,默認(rèn)為空格或tab。
- # 未排序, 所以沒有將所有的導(dǎo)出(join需要排序)
- ➜ shell-train > join -1 1 -2 2 city.code4 topcity.code
- 1201 天津市市轄區(qū) 197
- 1302 河北省唐山市 156
- 2301 黑龍江哈爾濱市 123
- 4201 湖北省武漢市 118
- 6101 陜西省西安市 100
- 6201 甘肅省蘭州市 59
- 6501 新疆烏魯木齊市 29
- 6523 新疆昌吉回族自治州 11
一定需要將結(jié)果輸出到文件,然后再進(jìn)行嗎?
其實(shí)也不一定。用管道的方式 | 可以將上一個(gè)命令的輸出結(jié)果作為下一個(gè)命令的輸入,可以通過 <(command) 的方式,將command 的輸出作為一個(gè)文件輸入。
- # 需要排序
- ➜ shell-train > join -1 1 -2 2 city.code4 <(head -n 10 topcity.code | sort -k 2)
- 1201 天津市市轄區(qū) 197
- 1301 河北省石家莊市 114
- 1302 河北省唐山市 156
- 1324 河北省保定地區(qū) 103
- 1501 內(nèi)蒙古呼和浩特市 88
- 2101 遼寧省沈陽市 109
- 2201 吉林省長春市 113
- 2301 黑龍江哈爾濱市 123
- 4201 湖北省武漢市 118
- 6101 陜西省西安市 100
舉個(gè)例子paste用來將兩個(gè)文件按列合并在一起:
- ➜ shell-train > cat paste.f1
- hello, i am
- world, you are
- ➜ shell-train > cat paste.f2
- tanglei, wechat is: tangleithu
- ?, hahaha
- ➜ shell-train > paste paste.f1 paste.f2
- hello, i am tanglei, wechat is: tangleithu
- world, you are ?, hahaha
以上用paste將兩個(gè)文件合并在一起了,實(shí)際上通過 <(cmd)的方式,可以不借助外部文件也能做到。
方法如下:
- ➜ shell-train > paste <(echo "hello, i am \nworld, you are") <(echo "tanglei, wechat is: tangleithu\n?, hahaha")
- hello, i am tanglei, wechat is: tangleithu
- world, you are ?, hahaha
本文轉(zhuǎn)載自微信公眾號「程序猿石頭」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系程序猿石頭公眾號。