2012-08-16 57 views
13

我有一個創建腳本的任務,它將一個巨大的文本文件作爲輸入。然後需要查找所有單詞和出現次數,並創建一個新文件,每行顯示一個唯一的單詞及其出現次數。是否有可能使這個shell腳本更快?

舉個例子取文件與此內容:

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud 
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure 
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt 
mollit anim id est laborum. 

我需要創建一個文件,該文件是這樣的:

1 AD 
1 ADIPISICING 
1 ALIQUA 
... 
1 ALIQUIP 
1 DO 
2 DOLOR 
2 DOLORE 
... 

爲此,我使用trsort寫了一個劇本, uniq

#!/bin/sh 
INPUT=$1 
OUTPUT=$2 
if [ -a $INPUT ] 
then 
    tr '[:space:][\-_?!.;\:]' '\n' < $INPUT | 
     tr -d '[:punct:][:special:][:digit:]' | 
     tr '[:lower:]' '[:upper:]' | 
     sort | 
     uniq -c > $OUTPUT 
fi 

這是幹什麼的es將空格分隔爲分隔符。如果這個詞包含-_?!.;:我將它們再次分解成單詞。我刪除了標點,特殊字符和數字,並將整個字符串轉換爲大寫。一旦完成,我將它分類並通過uniq傳遞給我想要的格式。

現在我下載了TXT格式的聖經,並用它作爲輸入。時序本我:

scripts|$ time ./text-to-word.sh text.txt b  
./text-to-word.sh text.txt b 16.17s user 0.09s system 102% cpu 15.934 total 

我做了一個Python腳本一樣:

import re 
from collections import Counter 
from itertools import chain 
import sys 

file = open(sys.argv[1]) 

c = Counter() 

for line in file.readlines(): 
    c.update([re.sub('[^a-zA-Z]', '', l).upper() 
      for l in chain(*[re.split('[-_?!.;:]', word) 
        for word in line.split()])]) 

file2 = open('output.txt', 'w') 
for key in sorted(c): 
    file2.write(key + ' ' + str(c[key]) + '\n') 

當我執行我拿到劇本:

scripts|$ time python text-to-word.py text.txt 
python text-to-word.py text.txt 7.23s user 0.04s system 97% cpu 7.456 total 

正如你可以看到它跑7.23s相比,在16.17s運行的shell腳本。我已經嘗試過更大的文件,並且總是Python似乎取得了勝利。我對上面的senario有幾個問題:

  1. 爲什麼Python腳本更快,因爲shell命令是用C編寫的?我意識到shell腳本可能不是最佳的腳本。
  2. 我該如何改進shell腳本?
  3. 我可以改進Python腳本嗎?

要清楚我沒有比較Python shell腳本。我並非試圖開始一場火焰戰爭,或者不需要任何其他語言的答案來比較自己的速度。使用管道小命令執行任務的UNIX哲學,我如何更快地創建shell腳本?

+5

我建議標題更改爲類似「是否有可能使這個shell腳本更快?「,使用如此不同的python腳本onl y作爲比較點。這將消除python和shell之間無用的和無關主題討論的風險。 – 2012-08-16 13:11:19

+5

我不認爲使用很多小命令很好地完成單個任務的\ nix理念已經到位,因爲它是最高效的*。它的原因是因爲使用我們的工具,您可以完成如此​​多的任務,爲您的簡單任務節省大量時間開發新程序。 – mgilson 2012-08-16 13:26:31

+1

Python也是用C編寫的。 「用C寫成」不足以讓事情變得更快 - 插入層(以及所有讀寫流水線和管線)都有開銷。 – 2012-08-16 14:15:55

回答

7

這裏重要的一點可能是進程間I/O。 Python腳本擁有內存中的所有數據,因此在處理數據時不會發生I/O。

另請注意,Python本身並不慢。 Python中的大多數功能都以C實現。

shell腳本必須啓動5個進程,並且每個進程都必須從stdin中讀取整個文本,並將整個文本寫入stdout四次。

有可能是一個方法,使Python腳本更快一點:你可以閱讀全文成一個字符串,然後刪除所有標點,分裂的話再算上他們:

text = file.read() 
text = re.sub(r'[.,:;-_]', '', text) 
text = text.upper() 
words = re.split(r'\\s+', text) 
c = Counter() 
c.update(words) 

那倒避免幾個嵌套循環的開銷。

至於shell腳本:你應該儘量減少進程的數量。這三個tr進程可能可以用sed的一個調用來取代。

+0

我的猜測是,最重要的因素是啓動許多子進程的開銷。 – 2012-08-16 13:44:46

+1

@SvenMarnach:No;總共涉及五個流程。開始他們將不到1秒,他的腳本運行16秒。 – 2012-08-16 13:56:31

+0

是的,你是對的。 (我之前已經提高了效率。) – 2012-08-16 14:16:26

3

這不是一種語言與另一種語言的問題。你的方法是不同的。

在Python中,您正在爲每個單詞增加一個計數器,然後迭代計數器以產生輸出。這將是O(n)。

在bash中,您將所有單詞分別放入一個長元組中,對元組進行排序,然後計算實例。這很可能是O(nlogn)。

+3

'計數器'仍然被排序,最好是'O(N * log(N))' – mgilson 2012-08-16 13:28:17

+0

計數器的n小於長元組的N,因爲有很多重複的東西 – 2012-08-16 15:57:16

+0

*你們都錯了。從Python文檔: *計數器是一個字典子類用於計算可哈希對象。它是一個無序的集合,其元素以字典鍵的形式存儲,並將其計數存儲爲字典值。 *計數器的時間順序仍爲N,因爲您必須檢查所有N個元素以獲取每個元素的計數。你說得對,計數器的記憶順序是K,其中K是唯一身份的數量。 – 2012-08-16 17:17:12

1

你可以提高你的bash腳本:

sed 's/[^a-zA-Z][^a-zA-Z]*/\'$'\n/g' <$INPUT | sort -f -u >$OUTPUT 

但短期和正確回答你的問題是:由於您使用的是完全不同的算法。

+0

謝謝,但您的腳本不會給我發生並且運行速度較慢。但你指出算法的區別是正確的。 – satran 2012-08-17 04:51:02

0

你可以試試這個:

考慮輸入文件是INPUT.TXT

bash腳本

cat Input.txt | tr [:space:] '\n' | grep -v "^\s*$" | sort | uniq -c | sort -bnr | tr [:lower:] [:upper:] 
0

一個使用GNU awk方式:

WHINY_USERS=1 awk '{ for (i=1; i<=NF; i++) { sub("[,.]","",$i); array[toupper($i)]++ } } END { for (j in array) print array[j], j }' file.txt 

僞/解釋:

## WHINY_USERS=1 enables sorting by keys. A bit of a trick. 
## Now loop through each word on each line, removing commas, full-stops, 
## adding each word in uppercase to an array. 
## Loop through the array printing vals and keys 

因人而異

0

一個bash解決方案

#!/bin/bash 
IFS=' -_?!.;\:,' 
while read -r line; do 
    for word in $line; do 
    word=${word//[^[:alpha:]]/} 
    [ $word ] || continue 
    word=$(tr '[:lower:]' '[:upper:]' <<<"$word") 
    ((_w_$word++)) 
    done 
done <"$INPUT" 
IFS=' ' 
for wword in ${!_w_*}; do echo "${!wword} ${wword#_w_}"; done > $OUTPUT.v1 

一個Perl高爾夫球解決方案

perl -nle '$h{uc()}++for/(\w+)/g}{print"$h{$_} $_"for sort keys%h' $INPUT > $OUTPUT.v2 
相關問題