2012-08-16 54 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 
相关问题