2016-06-14 32 views
5

Come posso utilizzare la bash sed comando per cambiare questa stringa:Come usare sed per sostituire la stringa multilinea?

<Directory /var/www/> 
    Options Indexes FollowSymLinks 
    AllowOverride None 
    Require all granted 
</Directory> 

nella seguente stringa? (solo cambiando la terza linea di stringa)

<Directory /var/www/> 
    Options Indexes FollowSymLinks 
    AllowOverride All 
    Require all granted 
</Directory> 

NOTA 1: Non voglio solo di indirizzare la stringa 'AllowOverride None' perché ci sono altri eventi nel file che non dovrebbe essere cambiato. Ho bisogno di indirizzare l'intera stringa a partire da <Directory /var/www>

NOTA 2: Devo anche sovrascrivere il file. Quindi, prendilo in considerazione nella tua risposta. E fornire diverse versioni per le versioni GNU/non GNU di sed solo nel caso in cui.

+1

Sarebbe bello avere un [mcve] più generico per vedere cosa è costante e cosa no: deve essere su '/ var/www'? Inoltre, usare 'sed' per multilinea non sembra essere il modo più pulito:' awk' probabilmente può gestire meglio questo modo. – fedorqui

risposta

0

Mi rendo conto che non è quello che hai chiesto da forse il suo valore non si utilizza sed?

Che ne dici di una soluzione python? Cammina directory passato come primo parametro per lo script e sostituisce esattamente<Directory elemento come lo hai scritto mentre solo cambiando None a All e scrive le modifiche al file. Funzionerà anche con diversi livelli di indentazione preservando il rientro originale. Funziona sia su python2 che su python3.

Dopo tutto, suppongo che se si è sed, probabilmente si ha anche python.

#!/usr/bin/env python 
import re 

r = re.compile(r'(<Directory /var/www/>\s+Options Indexes FollowSymLinks\s+AllowOverride)None(\s+Require all granted\s+</Directory>)', re.MULTILINE) 

for root, dirs, files in os.walk(sys.argv[1]): 
    for file_name in files: 
     if file_name.endswith('.conf'): 
      file_path = os.path.join(root, file_name) 
      with open(file_path) as fp: 
       data = r.sub(r'\1All\2', fp.read()) 
      with open(file_path, 'w+') as fp: 
       fp.write(data) 
7

Poiché i motivi contengono barre, utilizzare \% (per qualsiasi carattere %) per contrassegnare i motivi di ricerca. Quindi utilizzare:

sed -e '\%^<Directory /var/www/>%,\%^</Directory>% s/AllowOverride None/AllowOverride All/' 

I modelli di ricerca all'interno \%…% limite di ricerca linee tra i modelli corrispondenti, e la { s/…/…/; } cerca il motivo desiderato all'interno della gamma e rende la sostituzione appropriata.

Se non si desidera limitarlo a una singola sezione di directory ma a tutte le sezioni di directory, regolare il modello di avvio in modo appropriato. Ad esempio, questo corrisponderà a qualsiasi <Directory> sezione:

sed -e '\%^<Directory [^>]*>%,\%^</Directory>% s/AllowOverride None/AllowOverride All/' 

si può rendere più selettivo a seconda delle vostre esigenze.

+0

Non penso che OP intendesse limitare le sostituzioni a una sola sezione ** (ma a tutti ** ** sezioni). Inoltre, penso che questa soluzione sostituirà TUTTE le occorrenze di ** AllowOverride None ** tra (la prima) ** ** e l'ultima occorrenza del tag ** **. – Leon

+0

@Leon: se il nome della directory non ha importanza, rimuovilo dal primo pattern nell'intervallo di ricerca. No: gestiva separatamente ciascun elemento '' a ''; non userebbe prima ' 'come un singolo intervallo. –

+0

Sì, hai ragione. Ho avuto un refuso nella mia versione iniziale che ha provato proprio questo! – Leon

3

La versione semplice, basandosi sulla linea diAllowOverride proveniente entro due righe dopo < Directory ...> e utilizzando un'estensione sed GNU, è questa:

sed '/^<Directory/,+2 { s/AllowOverride None/AllowOverride All/g; }' 

UPDATE: Qui è la versione che non fa affidamento su nessuna estensione GNU (l'ho provata prima, ma ho fatto un refuso e sono rimasto sorpreso che non funzionasse, ecco perché l'altra versione è stata pubblicata prima):

sed '/^<Directory/,/^<\/Directory>/ { s/AllowOverride None/AllowOverride All/; }' 
1

Utilizzando Gnu Sed:

sed -zie 's!\(<Directory /var/www/>[^<]*AllowOverride\) None!\1 All!' ex1.txt 
  • Opzione -z è per Null separato record: tutto il file è un record, quindi basta fare una semplice sostituzione.
  • [^<]* L'espressione regolare (multilinea) rispetta i limiti della directory e consente un formato e un ordine flessibili.
1

La tua domanda è una buona illustrazione del mantra, non utilizzare sed.In realtà, non si dovrebbe usare alcun motore regex per un linguaggio context-free come XML. Ma puoi avvicinarti, magari abbastanza vicino, con awk.

#! /usr/bin/awk -f 

/<Directory \/var\/www\/>/ { 
    line = NR 
} 

/ AllowOverride None/ && line + 2 == NR { 
    gsub(/None/, "All") 
} 

{ print } 

In questo modo non si ha alcuna fantasia, regex non standard per leggere, e il codice dice esattamente che cosa significa: Se trovate "AllowOverride" 2 righe dopo la riga "Directory", sostituirlo. Le regex precedenti sono entrambe molto semplici (e compatibili con Posix) e dovrebbero funzionare con qualsiasi versione di awk.

0

La risposta è già data da questo user solo check here.

qualche riferimento

Nella convocazione semplice di sed, si ha una riga di testo nello spazio modello, vale a dire. 1 riga di testo delimitato da \ n dall'input. La singola linea nello spazio modello non ha \ n ... Ecco perché la tua espressione regolare non sta trovando nulla.

Puoi leggere più linee nello spazio modello e manipolare le cose sorprendentemente bene, ma con uno sforzo più che normale. Sed ha un set di comandi che permettono questo tipo di cose ... Ecco un link ad un Riepilogo comandi per sed. È il migliore che ho trovato e mi ha fatto rotolare.

Tuttavia, dimentica l'idea del "one-liner" una volta che inizi a utilizzare i micro-comandi di sed. È utile metterlo da parte come un programma strutturato fino a quando non ne hai la percezione ... È sorprendentemente semplice e ugualmente insolito. Si potrebbe pensare ad esso come al "linguaggio assemblatore" dell'editing di testo.

Sommario: Usa sed per le cose semplici, e forse un po 'di più, ma in generale, quando si arriva al di là di lavorare con una sola linea, la maggior parte delle persone preferisce qualcos'altro ... io sia qualcun altro a suggerire qualcosa di diverso .. sono davvero non sono sicuro che la scelta migliore sarebbe (userei sed, ma è perché non conosco abbastanza bene perl.)

sed '/^a test$/{ 
     $!{ N  # append the next line when not on the last line 
     s/^a test\nPlease do not$/not a test\nBe/ 
        # now test for a successful substitution, otherwise 
        #+ unpaired "a test" lines would be mis-handled 
     t sub-yes # branch_on_substitute (goto label :sub-yes) 
     :sub-not # a label (not essential; here to self document) 
        # if no substituion, print only the first line 
     P   # pattern_first_line_print 
     D   # pattern_ltrunc(line+nl)_top/cycle 
     :sub-yes # a label (the goto target of the 't' branch) 
        # fall through to final auto-pattern_print (2 lines) 
     }  
    }' alpha.txt 

Qui è lo stesso script, condensato in ciò che è ovviamente più difficile da leggere e lavorare, ma alcuni chiamerebbero dubbiamente un one-liner

sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;ty;P;D;:y}}' alpha.txt 

Ecco il mio comando "cheat-sheet"

: # label 
= # line_number 
a # append_text_to_stdout_after_flush 
b # branch_unconditional    
c # range_change      
d # pattern_delete_top/cycle   
D # pattern_ltrunc(line+nl)_top/cycle 
g # pattern=hold      
G # pattern+=nl+hold     
h # hold=pattern      
H # hold+=nl+pattern     
i # insert_text_to_stdout_now   
l # pattern_list      
n # pattern_flush=nextline_continue 
N # pattern+=nl+nextline    
p # pattern_print      
P # pattern_first_line_print   
q # flush_quit       
r # append_file_to_stdout_after_flush 
s # substitute           
t # branch_on_substitute    
w # append_pattern_to_file_now   
x # swap_pattern_and_hold    
y # transform_chars