2011-11-25 15 views
9

我有這個測試文件。使用sed替換文本只是在引號中

[[email protected] ~]# cat f.txt 
"a aa" MM "bbb b" 
MM MM 
MM"b b " 
[[email protected] ~]#

我想用引號替換引號中的所有空格字符,只是在引號中。所有不包括引號的字符都不應該被觸及。也就是說,我要的是類似於:

"a_aa" MM "bbb__b" 
MM MM 
MM"b_b_"

可以這樣使用的sed實施?

謝謝,

+2

順便說一句:好問題 - 特別是有很好的示例輸入和所需的輸出。 –

回答

8

這是一個完全不重要的問題。

這適用於下劃線代替,引號裏的第一空間:

$ sed 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' f.txt 
"a_aa" MM "bbb_ b" 
MM MM 
MM"b_b " 
$ 

在這個例子中,在裏面有任何的報價不超過兩個空間,人們很容易簡單地重複命令,但它給出了一個不正確的結果:

$ sed -e 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' \ 
>  -e 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' f.txt 
"a_aa"_ MM "bbb_ b" 
MM MM 
MM"b_b_" 
$ 

如果你的的sed版本支持「擴展正則表達式」,那麼這個工程的樣本數據:

$ sed -E \ 
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \ 
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \ 
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \ 
> f.txt 
"a_aa" MM "bbb__b" 
MM MM 
MM"b_b_" 
$ 

對於雙引號內的每個空格,你必須重複那個可怕的正則表達式 - 因此對於第一行數據來說是三次。

正則表達式可以如解釋:

  • 開始在一行的開頭,
  • 查找的「零個或多個非報價,隨後任選地報價,空格或引號序列和一個報價',整個組件重複零次或多次,
  • 後跟一個報價,零個或多個非引號,非空格,一個空格,零個或多個非引號和一個報價。
  • 用前導部分替換匹配材料,當前引用段落開始時的材料,下劃線和當前引用段落的尾部材料。

因爲起步錨的,這必須每空重複一次......但sed具有循環結構,所以我們可以做到這一點:

$ sed -E -e ':redo 
>   s/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/ 
>   t redo' f.txt 
"a_aa" MM "bbb__b" 
MM MM 
MM"b_b_" 
$ 

:redo定義了一個標籤; s///命令與以前一樣;如果自從上一次讀取一行或跳轉到標籤以來進行了任何替換,則t redo命令將跳轉到標籤。


鑑於該意見的討論中,有幾個值得一提的幾點:

  1. -E選項適用於sed在MacOS X(10.7.2測試)。GNU版本sed的相應選項是-r(或--regex-extended)。 -E選項與grep -E(它也使用擴展正則表達式)一致。 「經典Unix系統」不支持sed(Solaris 10,AIX 6,HP-UX 11)的ERE。

  2. 可以代替我用了?(這是強制使用的ERE,而不是BRE的唯一字符)與*,然後用括號(需要反斜槓在一個BRE他們面前處理使他們成爲捕獲括號),使腳本:

    sed -e ':redo 
         s/^\(\([^"]*\("[^ "]*"\)*\)*\)\("[^ "]*\) \([^"]*"\)/\1\4_\5/g 
         t redo' f.txt 
    

    這將產生相同的輸入相同的輸出 - 我試着輸入一些稍微複雜的圖案:

    "a aa" MM "bbb b" 
    MM MM 
    MM"b b " 
    "c c""d d""e e" X " f "" g " 
    "C C" "D D" "E E" x " F " " G " 
    

    氏s給出的輸出:

    "a_aa" MM "bbb__b" 
    MM MM 
    MM"b_b_" 
    "c_c""d_d""e__e" X "_f_""_g_" 
    "C_C" "D_D" "E__E" x "_F_" "_G_" 
    
  3. 即使BRE符號,sed支持\{0,1\}表示法指定0或1次出現先前RE術語,所以?版本可以使用被轉換爲BRE:

    sed -e ':redo 
         s/^\(\([^"]*\("[^ "]*"\)\{0,1\}\)*\)\("[^ "]*\) \([^"]*"\)/\1\4_\5/g 
         t redo' f.txt 
    

    這產生與其他選擇相同的輸出。

+0

謝謝你。優秀的解決方但是擴展的正則表達式開關在我的系統上是*** - r ***。 –

+0

@JonathanLeffler優秀的正則表達式使用,特別是'(「[^」] *「)?'碰撞替代,但爲什麼'?'而不是'*'? – potong

+0

我認爲你可以使用'?'或'* ''成功了('*'處理樣本數據)。我使用'?'是因爲它可能有助於限制正則表達式的回溯數量,這非常複雜(這不是我想要的正則表達式必須急於破譯!)。 –

0

一個莫名其妙不尋常的答案XSLT 2.0:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="2.0"> 
    <xsl:output method="text"></xsl:output> 
    <xsl:template name="init"> 
     <xsl:for-each select="tokenize(unparsed-text('f.txt'),'&#10;')"> 
      <xsl:for-each select="tokenize(.,'&quot;')"> 
       <xsl:value-of select="if (position() mod 2 = 0) 
        then concat('&quot;',translate(.,' ','_'),'&quot;') else ."></xsl:value-of> 
      </xsl:for-each> 
      <xsl:text>&#10;</xsl:text> 
     </xsl:for-each> 
    </xsl:template>  
</xsl:stylesheet> 

爲了測試是否,只得到sourceforge上saxon.jar並使用以下命令行:

java -jar saxon9.jar -it:init regexp.xsl 

XSLT文件包含對f.txt的引用,則文本文件必須與xslt文件位於同一目錄中。通過給樣式表一個參數可以很容易地改變它。

它在一次通過。

0

如果引用的文本全部在不同的行上,這將非常簡單。所以一種方法是分割文本,這樣你就可以做到,做簡單的轉換,然後重建線條。

拆分文本是容易的,但我們需要的是爲

  1. 已經被我們添加的文件
  2. 在目前的換行來區分

爲了做到這一點,我們可以用符號表示它屬於哪個類的每一行結束。我會用1和2,直接對應上面的。在SED,我們有:

sed -e 's/$/1/' -e 's/"[^"]*"/2\n&2\n/g' 

這將產生:

2 
"a aa"2 
    MM 2 
"bbb b"2 
1 
MM MM1 
MM2 
"b b "2 
1 

這很容易進行改造,只需使用

sed -e '/".*"/ s/ /_/g' 

2 
"a_aa"2 
    MM 2 
"bbb__b"2 
1 
MM MM1 
MM2 
"b_b_"2 
1 

最後,我們需要把它放回去。這實際上是在SED很可怕,但使用的保留空間是可行的:(這將是更清晰了很多,例如,AWK)

sed -e '/1$/ {s/1$//;H;s/.*//;x;s/\n//g}' -e '/2$/ {s/2$//;H;d}' 

管的三個步驟在一起,你就大功告成了。

0

這可能會爲你工作:

sed 's/^/\n/;:a;s/\(\n[^"]*"[^ "]*\) \([^"]*"\)\n*/\1_\2\n/;ta;s/\n//;ta;s/\n//' file 

說明:

前面加上一個\n到線的起點,這將被用來沿着換人磕碰。在"之內替換一個_,然後在那裏爲\n準備好下一輪替換。取代所有後,刪除\n並重復。當發生所有替換時,請刪除\n分隔符。

或該:

sed -r ':a;s/"/\n/;s/"/\n/;:b;s/(\n[^\n ]*) ([^\n]*\n)/\1_\2/g;tb;s/\n/%%%/g;ta;s/%%%/"/g' file 

說明:

「與\n小號的替換第一組""。用_替換換行符之間的第一個空格,重複。將\n替換爲一個唯一的分隔符(%%%),從頭開始重複。最後用"代替所有%%%

的第三種方式:

sed 's/"[^"]*"/\n&\n/g;$!s/$/@@@/' file | 
sed '/"/y/ /_/;1{h;d};H;${x;s/\n//g;s/@@@/\n/g;p};d' 

說明:

環繞所有引用的表達式("...")與換行符(\n的)。在除最後一行之外的所有行上插入行尾分隔符@@@。將結果傳遞給第二個sed命令。將的全部內容翻譯爲_,其中的內容爲"。將每條線存放在容納空間(HS)中。在文件中,交換到HS的結束,並刪除所有\n的,並與\n代替結束行分隔符的

最後:

sed 's/\("[^"]*"\)/$(tr '"' ' '_'"'<<<'"'"'\1'"'"')/g;s/^/echo /' file | sh 

或GNU sed的:

sed 's/\("[^"]*"\)/$(tr '"' ' '_'"'<<<'"'"'\1'"'"')/g;s/^/echo /e' file 

留給讀者解決。