2011-11-25 20 views
9

Ho questo file di test.usa sed per sostituire il testo tra virgolette

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

Voglio sostituire tutti i caratteri di spazio tra virgolette, notare, solo tra virgolette. Tutti i personaggi fuori dalle virgolette non devono essere toccati. Vale a dire, quello che voglio è qualcosa di simile a:

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

questo può essere implementato usando sed?

Grazie,

+2

BTW: buona domanda, specialmente con l'input di esempio e l'output richiesto. –

risposta

8

Questa è una domanda del tutto banale.

Questo funziona sostituendo il primo spazio interno citazioni con sottolineatura:

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

Per questo esempio, dove non ci sono più di due spazi all'interno di qualsiasi delle citazioni, si è tentati di ripetere semplicemente il comando, ma dà un risultato errato:

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

Se la versione di sed supporta 'esteso espressioni regolari', allora questo funziona per i dati di esempio:

$ 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_" 
$ 

È necessario ripetere questa espressione orribile per ogni spazio tra virgolette doppie, quindi tre volte per la prima riga di dati.

l'espressione regolare può essere spiegato come:

  • A partire dall'inizio di una riga,
  • Cercare sequenze di 'zero o più non-citazioni, eventualmente seguito da una citazione, senza spazi o virgolette , e una citazione ', l'intero assembly ripetuto zero o più volte,
  • Seguito da una citazione, zero o più non citazioni, non spazi, uno spazio e zero o più non citazioni e una citazione.
  • Sostituire il materiale adattato con la parte anteriore, il materiale all'inizio del passaggio quotato corrente, un trattino basso e il materiale finale del passaggio quotato corrente.

causa dell'ancora partenza, questa deve essere ripetuta una volta al vuoto ... ma sed ha un costrutto iterativo, in modo che possiamo farlo con:

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

Il :redo definisce un'etichetta; il comando s/// è come prima; il comando t redo salta all'etichetta se è stata eseguita una sostituzione dall'ultima lettura di una linea o passando a un'etichetta.


Data la discussione nei commenti, ci sono un paio di punti degni di nota:

  1. L'opzione -E applica a sed su MacOS X (testato 10.7.2).L'opzione corrispondente per la versione GNU di sed è -r (o --regex-extended). L'opzione -E è coerente con grep -E (che utilizza anche le espressioni regolari estese). I "sistemi Unix classici" non supportano ERE con sed (Solaris 10, AIX 6, HP-UX 11).

  2. è possibile sostituire il ? ho usato (che è l'unico personaggio che impone l'utilizzo di un ERE invece di un BRE) con *, e poi trattare con le parentesi (che richiedono backslash davanti a loro in un BRE per trasformarli in parentesi cattura), lasciando lo script:

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

    Ciò produce lo stesso output sullo stesso input - ho provato alcuni modelli leggermente più complessi in ingresso:

    "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 " 
    

    Thi s dà l'output:

    "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. Anche con BRE notazione, sed supportata la notazione \{0,1\} per specificare 0 o 1 occorrenze del termine RE precedente, così la versione ? potrebbe tradurre in una BRE utilizzando:

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

    Questo produce la stessa uscita delle altre alternative.

+0

Grazie. Ottima soluzione. Ma lo switch di espressioni regolari esteso è *** - r *** sul mio sistema. –

+0

@JonathanLeffler ottimo uso della regex, in particolare '(" [^ "] *")? 'Per imbattersi nella sostituzione ma perché'? 'E non' * '? – potong

+0

Penso che sia possibile usare'? 'O' * 'con successo (' * 'funziona sui dati di esempio). Ho usato'? 'perché potrebbe aiutare a limitare la quantità di backtracking da eseguire nella regex, che è piuttosto complessa. (Non è un'espressione regolare che vorrei dover decifrare in fretta!). –

0

Una risposta in qualche modo inusuale in 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> 

per verificare se, solo ottenere saxon.jar su sourceforge e utilizzare la seguente riga di comando:

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

Il file XSLT includere il riferimento a f.txt, il file di testo deve essere nella stessa directory del file xslt. Questo può essere facilmente modificato assegnando un parametro al foglio di stile.

Funziona in un passaggio.

0

Questo sarebbe davvero facile se il testo citato fosse tutto su righe separate. Quindi un approccio è quello di dividere il testo in modo da avere quello, fare la facile trasformazione, quindi ricostruire le linee.

Splitting il testo è facile, ma avremo bisogno di distinguere tra nuove righe che erano

  1. già presente nel file
  2. aggiunto da noi

Per fare ciò, possiamo termina ogni riga con un simbolo che indica a quale classe appartiene. Userò solo 1 e 2, corrispondente direttamente a quanto sopra.In sed, abbiamo:

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

Questo produce:

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

che è facile da trasformare, basta usare

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

dando

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

Infine, abbiamo bisogno di rimettilo insieme. Questo è in realtà piuttosto orribile in sed, ma fattibile utilizzando lo spazio stiva: (. Questo sarebbe molto più chiaro in, ad esempio, awk)

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

tubo quei tre passi insieme e il gioco è fatto .

0

questi potrebbero funzionare per voi:

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

Spiegazione:

anteporre una \n alla partenza della linea, questo sarà usato per urtare lungo le sostituzioni. Sostituisci un singolo con uno all'interno dello " e mentre è lì, un \n pronto per il prossimo turno di sostituzioni. Dopo aver sostituito tutti gli , eliminare \n e ripetere. Quando tutte le sostituzioni si sono verificate, eliminare il delimitatore \n.

o questo:

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

Spiegazione:

Sostituire la prima serie di "" 's con \n' s. Sostituisci il primo spazio tra le nuove linee con _, ripeti. Sostituire \n con un delimitatore univoco (%%%), ripetere dall'inizio. Riordina alla fine sostituendo tutto %%% con ".

Un terzo modo:

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

Spiegazione:

Surround tutte le espressioni citate ("...") con a capo (\n 's). Inserisci un delimitatore di fine riga @@@ su tutto tranne l'ultima riga. Risultato tubo al secondo comando sed. Tradurre tutti gli 's a _' s per le linee con un " in loro. Memorizza ogni riga nello spazio di attesa (HS).Alla fine del file, di swap al HS ed eliminare tutti i \n 's e sostituire delimitatori di fine linea con \n' s

infine:

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

o GNU sed:

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

lasciato andare al lettore.

Problemi correlati