2011-07-24 134 views
5

我有一個項目,我給了一個文件,我需要從文件中提取字符串。基本上想到在Linux中的「字符串」命令,但我在python中這樣做。下一個條件是文件以流(例如字符串)的形式提供給我,所以使用其中一個子進程函數運行字符串的明顯答案也不是一個選項。從Python中的二進制文件中提取字符串

我寫了這個代碼:

def isStringChar(ch): 
    if ord(ch) >= ord('a') and ord(ch) <= ord('z'): return True 
    if ord(ch) >= ord('A') and ord(ch) <= ord('Z'): return True 
    if ord(ch) >= ord('0') and ord(ch) <= ord('9'): return True 

    if ch in ['/', '-', ':', '.', ',', '_', '$', '%', '\'', '(', ')', '[', ']', '<', '>', ' ']: return True 

# default out 
return False 

def process(stream): 
dwStreamLen = len(stream) 
if dwStreamLen < 4: return None 

dwIndex = 0; 
strString = '' 
for ch in stream: 
    if isStringChar(ch) == False: 
     if len(strString) > 4: 
      #print strString 
      strString = '' 
    else: 
     strString += ch 

這種技術上的工作,但WAY緩慢。例如,我能夠在500Meg可執行文件上使用strings命令,並在不到1秒的時間內生成了價值300K的字符串。我通過上面的代碼運行了相同的文件,花了16分鐘。

在那裏有一個庫,可以讓我在沒有python延遲的情況下執行此操作嗎?

謝謝!

+0

如果您可以閱讀C [GNU字符串的源代碼](http://sourceware.org /cgi-bin/cvsweb.cgi/src/binutils/strings.c?rev=1.48&content-type=text/x-cvsweb-markup&cvsroot=src)可能會有幫助。它只有幾百行,所以沒那麼糟糕。 –

回答

7

與David Wolever's相似的速度,使用re,Python的正則表達式庫。優化的一個小故事是,你寫的代碼越少,速度就越快。循環的庫函數通常以C語言實現,並且速度比您希望的要快。 char in set()比檢查自己快。 Python在這方面與C相反。

import sys 
import re 

chars = r"A-Za-z0-9/\-:.,_$%'()[\]<> " 
shortest_run = 4 

regexp = '[%s]{%d,}' % (chars, shortest_run) 
pattern = re.compile(regexp) 

def process(stream): 
    data = stream.read() 
    return pattern.findall(data) 

if __name__ == "__main__": 
    for found_str in process(sys.stdin): 
     print found_str 

在4K的塊工作計劃很巧妙,但在邊緣案件棘手一點與re。 (其中兩個字符位於4k塊的末尾,而下一個2位於下一個塊的開頭)

+0

不錯的建議。我同意 - 對於較小的流(即,可以適應內存),這當然是可取的。我敢打賭,你甚至可以把這個流塊分成幾塊,然後把這個正則表達式在不可打印的字符上分割出來,然後運行這個正則表達式......嗯...... –

+0

@dougallj:這是非常快的。謝謝!現在,如果你可以讓它找到unicode字符串,我會給你買一瓶啤酒;-)我不會有(並且顯然沒有)想過使用re。我能夠在33秒內處理我的500Meg測試文件。這完全在我的設計規格範圍內。 – tjac

+0

@tjac:什麼是unicode字符串? –

5

至少有一個問題是你正在讀整個流到內存中(… = len(stream)),另一個是你的isStringChar函數非常慢(函數調用相對較慢,而且你做了很多他們)。

更好的方式是這樣的:

import sys 
import string 

printable = set(string.printable) 

def process(stream): 
    found_str = "" 
    while True: 
     data = stream.read(1024*4) 
     if not data: 
      break 
     for char in data: 
      if char in printable: 
       found_str += char 
      elif len(found_str) >= 4: 
       yield found_str 
       found_str = "" 
      else: 
       found_str = "" 

if __name__ == "__main__": 
    for found_str in process(sys.stdin): 
     print found_str 

這將是更快,因爲:

  • 的「是字符打印的」查找與一個查找執行(和O(1)操作)直接調用(如果我沒有弄錯)到C函數(這將是非常快)。
  • 流以4k塊處理,這將改善大型輸入的內存使用和運行時,因爲不需要交換。
+2

您需要一個'else:found_str =「」'。代碼當前會打印它所絆倒的任何4個可打印字符,而不管它們是否連續。 – dougallj

+0

D'oh。謝謝。現在修復。 (如果這並不明顯,我實際上沒有*測試*此代碼...) –

相關問題