2010-09-24 72 views
3

編輯:我真的只是好奇,我怎麼能得到這個正則表達式的工作。請不要告訴我有更簡單的方法來做到這一點。這很明顯! :P正則表達式來解析註釋的配置文件

我正在寫一個正則表達式(使用Python)來解析配置文件中的行。行看起來是這樣的:

someoption1 = some value # some comment 
# this line is only a comment 
someoption2 = some value with an escaped \# hash 
someoption3 = some value with a \# hash # some comment 

的想法是,散列符號之後什麼被認爲是一個評論,除非該散列以斜線逃脫。

我試圖用正則表達式將每一行分成它的各個部分:引導空白,賦值的左側,賦值的右側和註釋。對於本例中的第一行中,擊穿將是:

  • 空白: 「」
  • 分配左: 「someoption1 =」
  • 分配權: 「一些值」
  • 評論「#一些評」

這是正則表達式我到目前爲止:

^(\s)?(\S+\s?=)?(([^\#]*(\\\#)*)*)?(\#.*)?$ 

我很喜歡正則表達式,所以隨時把它撕開!

使用Python的re.findAll(),這是回訪:

  • 第0指數:空白,因爲它應該是
  • 第一個指數:分配
  • 第二個指標的左側:右側(不正確)
  • 第5個索引:第一個散列,不論是否轉義,以及其後的任何內容(這是不正確的)

對於我缺少的正則表達式,可能有一些基本的東西。如果有人可以解決這個問題,我會永遠感謝...

+0

這個問題是關於正則表達式還是關於用Python解析配置文件?如果是後者,那你爲什麼要編寫一個配置文件解析器? Python的標準ConfigParser模塊(http://docs.python.org/library/configparser.html)應該可以做到! – 2010-09-24 01:41:34

+0

只是專門詢問那個正則表達式。我只是想知道如何使用正則表達式來做到這一點。我意識到還有其他一些方法可以實現相同的目標,包括Python內置的configparser模塊。雖然謝謝! – apeace 2010-09-24 01:52:28

+1

如果你不從頭開始提供不是無能的證據,回答者將承擔最壞的情況:-) – 2010-09-24 02:19:49

回答

2

正則表達式不匹配的原因是因爲正則表達式的貪婪匹配行爲:每個部分將匹配最長的子字符串,使得字符串的其餘部分仍然可以與正則表達式

這意味着您的一條線路與一名逃犯#的情況什麼的其餘部分是:

  • [^\#]*(有沒有必要BTW逃跑#)將第一哈希之前匹配所有,其中包括反斜槓
  • (\\\#)*將不匹配任何東西,因爲在這一點上字符串以#
  • 最後(\#.*)開始將匹配字符串

一個簡單的例子的其它部分來強調這種潛在的非直觀行爲:在正則表達式(a*)(ab)?(b*),則(ab)?絕不會匹配任何

我相信這個正則表達式(在原有基礎上的)應該工作:^\s*(\S+\s*=([^\\#]|\\#?)*)?(#.*)?$

+0

感謝您的信息! – apeace 2010-09-24 02:12:56

+0

呵呵,哈希必須在Python中使用正則表達式。現在你知道了! – apeace 2010-09-24 02:13:17

+0

@apeace真的嗎? Python的文檔沒有提到這一點,我似乎可以使用未轉義的#s而沒有問題... – dave 2010-09-24 02:21:32

0

我不會用這個正則表達式,因爲同樣的原因,我不會嘗試用熱核彈頭殺死蒼蠅。

假設你正在讀線的時間,只是:

  • 如果第一個字符是一個#,設置爲全行註釋和空行。
  • 否則,請在\之後找到#的第一個匹配項,然後將註釋設置爲加上該行的其餘部分,並將該行設置爲之前的所有內容。
  • #替換所有出現的\#

就是這樣,你現在有一個正確的線和評論部分。一定要用正則表達式來分割新的線段。

例如:

import re 

def fn(line): 
    # Split line into non-comment and comment. 

    comment = "" 
    if line[0] == "#": 
     comment = line 
     line = "" 
    else: 
     idx = re.search (r"[^\\]#", line) 
     if idx != None: 
      comment = line[idx.start()+1:] 
      line = line[:idx.start()+1] 

    # Split non-comment into key and value. 

    idx = re.search (r"=", line) 
    if idx == None: 
     key = line 
     val = "" 
    else: 
     key = line[:idx.start()] 
     val = line[idx.start()+1:] 
    val = val.replace ("\\#", "#") 

    return (key.strip(),val.strip(),comment.strip()) 

print fn(r"someoption1 = some value # some comment") 
print fn(r"# this line is only a comment") 
print fn(r"someoption2 = some value with an escaped \# hash") 
print fn(r"someoption3 = some value with a \# hash # some comment") 

生產:

('someoption1', 'some value', '# some comment') 
('', '', '# this line is only a comment') 
('someoption2', 'some value with an escaped # hash', '') 
('someoption3', 'some value with a # hash', '# some comment') 

如果必須使用正則表達式(針對我的意見),您的具體問題就在這裏:

[^\#] 

(假設您的意思是正確轉義的r"[^\\#]")將嘗試匹配除\#之外的任何字符,而不是您想要的順序\#。您可以使用排除查找屁股做到這一點,但我總是說,一旦正則表達式變得不可讀的白癡着急,最好恢復到程序代碼:-)


經過思考,一更好的方式來做到這一點是一個多層次的分裂(這樣的正則表達式沒有獲得通過處理丟失的領域太可怕),具體如下:

def fn(line): 
    line = line.strip()       # remove spaces 
    first = re.split (r"\s*(?<!\\)#\s*", line, 1) # get non-comment/comment 
    if len(first) == 1: first.append ("")   # ensure we have a comment 
    first[0] = first[0].replace("\\#","#")   # unescape non-comment 

    second = re.split (r"\s*=\s*", first[0], 1) # get key and value 
    if len(second) == 1: second.append ("")  # ensure we have a value 
    second.append (first[1])      # create 3-tuple 
    return second         # and return it 

它使用負前瞻正確匹配註釋分隔符將非註釋位分隔爲鍵和值。空格可以在這一個正確處理爲好,得到以下特性:

['someoption1', 'some value', 'some comment'] 
['', '', 'this line is only a comment'] 
['someoption2', 'some value with an escaped # hash', ''] 
['someoption3', 'some value with a # hash', 'some comment'] 
+0

採取了點。我意識到用另一種方式寫它會很容易:P我主要只是想知道爲什麼該正則表達式不起作用。這就是爲什麼我問這個問題! – apeace 2010-09-24 01:51:27

+0

此解決方案(使用負向後視)不起作用。看到我的評論約翰Machin的答案遭受同樣的問題。 – ridgerunner 2011-03-12 22:03:56

1

我已經離開這個問題的目的發表評論,但假設這個問題是純粹的正則表達式,我還是會給出答案一射擊。

假設你一次處理一行輸入,我會把它作爲一個兩遍處理。這意味着你將有2個正則表達式。

  1. 沿東西的(.*?(?<!\\))#(.*)行:分割在第一#不受\ preceeded(見負回顧後文檔);
  2. 賦值語句表達式解析。
+0

這似乎是我正在尋找。我會去查找負面的後顧之道。謝謝你的提示! – apeace 2010-09-24 01:56:15

+0

此解決方案(使用負向倒序)不起作用。看到我的評論約翰Machin的答案遭受同樣的問題。 – ridgerunner 2011-03-12 21:54:18

0

試着將它分解成兩個步驟:

  1. 轉義處理,以識別真正的意見(第一#不\(提示前面:「負回顧後發」)),除去真實的意見,然後更換r"\#""#"

  2. 處理無評論剩餘部分。

BIG HINT:use re。VERBOSE與評論

+0

在這種情況下,負向lookbehind不起作用。沒有辦法知道在#之前有多少反斜槓,並且計數的含義發生了變化。 (例如'#'和'\\#'和'\\\\#'開始註釋,而'\#'和'\\\#'不需要)。Gumbo的解決方案演示瞭解決這個問題的正確方法。但我同意1000%關於您的「VERBOSE模式與評論」的建議! – ridgerunner 2011-03-12 21:46:17

2

我會在多行模式使用正則表達式:

^\s*([a-zA-Z_][a-zA-Z_0-9]*)\s*=\s*((?:[^\\#]|\\.)+) 

這允許任何字符轉義(\\.)。如果您只想要允許#,請改爲使用\\#

+0

這是一個正確的解決方案,實際工作。它只是缺少一個表達式來匹配值後面的可選註釋,以及字符串末尾的'$'錨點。此外,通過應用Friedl的「* unrolling-the-loop *」技術,'(?:[^ \\#] | \\。)+'子表達式可以更加高效*'[^# \] *(?:\\ [^#\\] *)*' – ridgerunner 2011-03-12 21:28:26

2

到目前爲止提出的5個解決方案中,只有Gumbo的實際工作。這裏是我的解決方案,這也適用,並且大量註釋:

import re 

def fn(line): 
    match = re.search(
     r"""^   # Anchor to start of line 
     (\s*)   # $1: Zero or more leading ws chars 
     (?:   # Begin group for optional var=value. 
      (\S+)  # $2: Variable name. One or more non-spaces. 
      (\s*=\s*) # $3: Assignment operator, optional ws 
      (   # $4: Everything up to comment or EOL. 
      [^#\\]* # Unrolling the loop 1st normal*. 
      (?:  # Begin (special normal*)* construct. 
       \\.  # special is backslash-anything. 
       [^#\\]* # More normal*. 
      )*   # End (special normal*)* construct. 
     )   # End $4: Value. 
     )?    # End group for optional var=value. 
     ((?:\#.*)?) # $5: Optional comment. 
     $    # Anchor to end of line""", 
     line, re.MULTILINE | re.VERBOSE) 
    return match.groups() 

print (fn(r" # just a comment")) 
print (fn(r" option1 = value")) 
print (fn(r" option2 = value # no escape == IS a comment")) 
print (fn(r" option3 = value \# 1 escape == NOT a comment")) 
print (fn(r" option4 = value \\# 2 escapes == IS a comment")) 
print (fn(r" option5 = value \\\# 3 escapes == NOT a comment")) 
print (fn(r" option6 = value \\\\# 4 escapes == IS a comment")) 

上述腳本生成以下(正確)的輸出:(與Python 3.0.1測試)

(' ', None, None, None, '# just a comment') 
(' ', 'option1', ' = ', 'value', '') 
(' ', 'option2', ' = ', 'value ', '# no escape == IS a comment') 
(' ', 'option3', ' = ', 'value \\# 1 escape == NOT a comment', '') 
(' ', 'option4', ' = ', 'value \\\\', '# 2 escapes == IS a comment') 
(' ', 'option5', ' = ', 'value \\\\\\# 3 escapes == NOT a comment', '') 
(' ', 'option6', ' = ', 'value \\\\\\\\', '# 4 escapes == IS a comment') 

請注意,此解決方案使用Jeffrey Friedl的「展開循環效率技術(消除慢速交替),它也不使用環視,且速度非常快。聲稱「知道」正則表達式。 (當我說「知道」,我的意思是在Neo「我知道功夫!」感:) :)