Esci da una stringa per un modello di sostituzione sed


317

Nel mio script bash ho una stringa esterna (ricevuta dall'utente), che dovrei usare nel modello sed.

REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"

Come posso sfuggire al $REPLACE stringa in modo che possa essere tranquillamente accettata sedcome una sostituzione letterale?

NOTA: Il KEYWORDè una stringa muto con nessun risultato, ecc E non venga fornita dall'utente.


13
Stai cercando di evitare il problema dei "Tavolini Bobby" se dicono "/ g -e '/ PASSWORD =. * / PASSWORD = abc / g'"?
Paul Tomblin,

2
Se usi bash, non hai bisogno di sed. Basta usareoutputvar="${inputvar//"$txt2replace"/"$txt2replacewith"}".
destenson il

@destenson Penso che non dovresti mettere le due variabili fuori dalle virgolette. Bash può leggere le variabili tra virgolette (nel tuo esempio, gli spazi bianchi potrebbero rovinare le cose).
Camilo Martin,


1
@CamiloMartin, vedi il mio commento sulla mia risposta. Le virgolette all'interno di $ {} non corrispondono alle virgolette all'interno. Le due variabili non sono al di fuori delle virgolette.
destenson,

Risposte:


268

Avvertenza : questo non considera le nuove righe. Per una risposta più approfondita, vedere invece questa domanda SO . (Grazie, Ed Morton e Niklas Peter)

Nota che fuggire da tutto è una cattiva idea. Sed ha bisogno di sfuggire a molti personaggi per ottenere il loro significato speciale. Ad esempio, se si evita una cifra nella stringa di sostituzione, si trasformerà in un riferimento indietro.

Come ha detto Ben Blank, ci sono solo tre caratteri che devono essere sfuggiti nella stringa di sostituzione (sfugge a se stessi, barra in avanti per la fine dell'istruzione e & per sostituire tutto):

ESCAPED_REPLACE=$(printf '%s\n' "$REPLACE" | sed -e 's/[\/&]/\\&/g')
# Now you can use ESCAPED_REPLACE in the original sed statement
sed "s/KEYWORD/$ESCAPED_REPLACE/g"

Se mai dovessi sfuggire al KEYWORD stringa, quella che ti serve è la seguente:

sed -e 's/[]\/$*.^[]/\\&/g'

E può essere utilizzato da:

KEYWORD="The Keyword You Need";
ESCAPED_KEYWORD=$(printf '%s\n' "$KEYWORD" | sed -e 's/[]\/$*.^[]/\\&/g');

# Now you can use it inside the original sed statement to replace text
sed "s/$ESCAPED_KEYWORD/$ESCAPED_REPLACE/g"

Ricorda, se usi un carattere diverso da /come delimitatore, devi sostituire la barra nelle espressioni sopra con il carattere che stai usando. Vedi il commento di PeterJCLaw per una spiegazione.

Modificato: a causa di alcuni casi angolari precedentemente non considerati, i comandi sopra sono cambiati più volte. Controlla la cronologia delle modifiche per i dettagli.


17
Vale la pena notare che è possibile evitare di sfuggire alle barre in avanti non utilizzandole come delimitatori. La maggior parte (tutte?) Le versioni di sed ti permettono di usare qualsiasi personaggio, purché si adatti allo schema: $ echo 'foo / bar' | sed s _ / _: _ # foo: bar
PeterJCLaw,

2
sed -e 's / (\ / \ | \\\ | &) / \\ & / g' non ha funzionato per me su OSX ma questo fa: sed 's / ([\\\ / &]) / \\ & / g 'ed è leggermente più corto.
jcoffland,

1
Per il modello di ricerca KEYWORD, in GNU sed , ecco altri 2 caratteri ^, $non menzionati sopra:s/[]\/$*.^|[]/\\&/g
Peter.O

1
@Jesse: risolto. In realtà, questo è l'errore a cui metto in guardia nel primo paragrafo. Immagino di non praticare ciò che predico.
Pianosaurus,

1
@NeronLeVelu: Non sono sicuro di sapere cosa intendi, ma "non ha alcun significato speciale in pipe o variabili. Viene analizzato dalla shell prima di eseguire il risultato, quindi le doppie virgolette all'interno delle variabili sono sicure. Ad esempio, prova a eseguire A='foo"bar' echo $A | sed s/$A/baz/in bash. Le doppie virgolette vengono trattate proprio come il "foo" e la "barra" attorno ad esso.
Pianosaurus,

92

Il comando sed consente di utilizzare altri caratteri anziché /come separatore:

sed 's#"http://www\.fubar\.com"#URL_FUBAR#g'

Le doppie virgolette non sono un problema.


5
Hai ancora bisogno di scappare .che altrimenti ha un significato speciale. Ho modificato la tua risposta.
ypid

Ho appena provato a fare: sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' filecon sed '|CLIENTSCRIPT="foo"|a CLIENTSCRIPT2="hello"' filee che non fa lo stesso.
Dimitri Kopriwa,

1
Poiché questo vale solo per il sostituto, questo dovrebbe essere il seguente: Il scomando (come in sostituzione) di sed consente di utilizzare altri caratteri anziché / come separatore. Inoltre, questa sarebbe una risposta a come utilizzare sed su URL con caratteri di barra. Non risponde alla domanda OP su come evitare una stringa immessa da un utente, che potrebbe contenere /, \, ma anche # se si decide di usarla. Inoltre, l'URI può contenere anche #
papo,

2
mi ha cambiato la vita! Grazie!
Franciscon Santos,

48

Gli unici tre caratteri letterali trattati in modo speciale nella clausola di sostituzione sono /(per chiudere la clausola), \(per sfuggire a caratteri, backreference, ecc.) E &(per includere la corrispondenza nella sostituzione). Pertanto, tutto ciò che devi fare è scappare da questi tre personaggi:

sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"

Esempio:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar

Anche una newline, penso. Come posso sfuggire a una nuova linea?
Alexander Gladysh,

2
Prestare attenzione al comportamento predefinito dell'eco rispetto alle barre rovesciate. In bash, per impostazione predefinita l'eco non interpreta le fughe di backslash, che servono allo scopo qui. Nel trattino (sh), d'altra parte, l'eco interpreta le fughe di barra rovesciata e, per quanto ne so, non ha modo di sopprimerlo. Pertanto, in trattino (sh), anziché echo $ x, stampa '% s \ n' $ x.
Youssef Eldakar,

Inoltre, usa sempre l'opzione -r quando fai una lettura per trattare le barre rovesciate nell'input dell'utente come valori letterali.
Youssef Eldakar,

Per la compatibilità multipiattaforma con altre shell, è necessario consultare questo documento relativo alla sostituzione dei caratteri speciali sed: grymoire.com/Unix/Sed.html#toc-uh-62
Dejay Clayton

2
@Drux I tre caratteri sono gli unici nella clausola di sostituzione . Molto di più è speciale nella clausola del modello.
lenz

33

Basato sulle espressioni regolari di Pianosaurus, ho creato una funzione bash che sfugge sia alla parola chiave che alla sostituzione.

function sedeasy {
  sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

Ecco come lo usi:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf

3
Grazie! se qualcun altro riceve un errore di sintassi quando tenta di usarlo, proprio come me, ricordati di eseguirlo usando bash, non sh
Konstantin Pereiaslov

1
Esiste una funzione solo per sfuggire a una stringa per sed invece di avvolgersi attorno a sed?
CMCDragonkai,

Ehi, solo un avvertimento generale sull'avvio di pipe con un'eco come questa: alcune (la maggior parte?) Implementazioni di echo prendono opzioni (vedi man echo), facendo in modo che la pipe si comporti inaspettatamente quando la tua discussione $1inizia con un trattino. Invece, puoi iniziare la pipa con printf '%s\n' "$1".
Pianosaurus,

17

È un po 'tardi per rispondere ... ma c'è un modo molto più semplice per farlo. Basta cambiare il delimitatore (cioè il carattere che separa i campi). Quindi, invece di s/foo/bar/scrivere s|bar|foo.

Ed ecco il modo semplice per farlo:

sed 's|/\*!50017 DEFINER=`snafu`@`localhost`\*/||g'

L'output risultante è privo di quella brutta clausola DEFINER.


10
No, &e `` deve ancora essere evitato, come deve essere il delimitatore, qualunque sia la scelta.
mirabilos

3
Ciò ha risolto il mio problema, poiché avevo caratteri "/" in una stringa di sostituzione. Grazie uomo!
Evgeny Goldin,

per me va bene. Quello che sto facendo è provare a fuggire $nella stringa che sta per essere modificata e mantenere il significato di $nella stringa di sostituzione. dire che voglio cambiare $XXXil valore della variabile $YYY, sed -i "s|\$XXX|$YYY|g" filefunziona bene.
Hakunami,

11

Si scopre che stai facendo la domanda sbagliata. Ho anche fatto la domanda sbagliata. Il motivo per cui è sbagliato è l'inizio della prima frase: "Nel mio script bash ...".

Ho avuto la stessa domanda e ho commesso lo stesso errore. Se stai usando bash, non è necessario usare sed per eseguire sostituzioni di stringhe (ed è molto più pulito usare la funzione di sostituzione integrata in bash).

Invece di qualcosa di simile, ad esempio:

function escape-all-funny-characters() { UNKNOWN_CODE_THAT_ANSWERS_THE_QUESTION_YOU_ASKED; }
INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A="$(escape-all-funny-characters 'KEYWORD')"
B="$(escape-all-funny-characters '<funny characters here>')"
OUTPUT="$(sed "s/$A/$B/g" <<<"$INPUT")"

puoi usare esclusivamente le funzionalità bash:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"

A proposito, l'evidenziazione della sintassi qui è sbagliata. Le citazioni esterne corrispondono e le citazioni interne corrispondono. In altre parole, sembra $Ae $Bnon sono quotati, ma non lo sono. Le virgolette al suo interno ${}non corrispondono alle virgolette al di fuori di esso.
destenson,

In realtà non devi citare il lato destro di un compito (a meno che tu non voglia fare qualcosa del genere var='has space') - OUTPUT=${INPUT//"$A"/"$B"}è sicuro.
Benjamin W.

In realtà non devi citare il lato destro di un compito (a meno che tu non voglia che funzioni nel mondo reale e non solo come una sceneggiatura giocattolo per mostrare la tua folle skilz). Cerco sempre di citare ogni espansione variabile che non voglio interpretare dalla shell, a meno che non abbia un motivo specifico per non farlo. In questo modo, le cose tendono a rompersi meno spesso, soprattutto se fornite di input nuovi o imprevisti.
destenson,

1
Vedere il manuale : "Tutti i valori subiscono espansione tilde, espansione parametri e variabili, sostituzione comandi, espansione aritmetica e rimozione virgolette (dettagliata di seguito)." Cioè, lo stesso delle doppie virgolette.
Benjamin W.

1
Cosa succede se è necessario utilizzare sed su un file?
Efren,

1

Usa awk - è più pulito:

$ awk -v R='//addr:\\file' '{ sub("THIS", R, $0); print $0 }' <<< "http://file:\_THIS_/path/to/a/file\\is\\\a\\ nightmare"
http://file:\_//addr:\file_/path/to/a/file\\is\\\a\\ nightmare

2
Il problema awkè che non ha nulla di simile sed -i, il che risulta estremamente utile il 99% delle volte.
Tino,

Questo è un passo nella giusta direzione, ma awk interpreta ancora alcuni metacaratteri nella tua sostituzione, quindi non è ancora sicuro per l'input dell'utente.
Jeremy Huiskamp,

0

Ecco un esempio di un AWK che ho usato qualche tempo fa. È un AWK che stampa nuovi AWKS. Essendo AWK e SED simili potrebbe essere un buon modello.

ls | awk '{ print "awk " "'"'"'"  " {print $1,$2,$3} " "'"'"'"  " " $1 ".old_ext > " $1 ".new_ext"  }' > for_the_birds

Sembra eccessivo, ma in qualche modo quella combinazione di virgolette funziona per mantenere la stampa come letterale. Quindi, se ricordo bene, i vaiable sono racchiusi tra virgolette come questa: "$ 1". Provalo, fammi sapere come funziona con SED.


0

Ho un miglioramento rispetto alla funzione sedeasy, che si romperà con caratteri speciali come tab.

function sedeasy_improved {
    sed -i "s/$(
        echo "$1" | sed -e 's/\([[\/.*]\|\]\)/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/$(
        echo "$2" | sed -e 's/[\/&]/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/g" "$3"
}

Quindi, cosa c'è di diverso? $1e$2 racchiuso tra virgolette per evitare espansioni della shell e preservare schede o doppi spazi.

Piping aggiuntivo | sed -e 's:\t:\\t:g'(mi piace :come token) che trasforma una scheda in \t.


Ma vedi il mio commento sulla risposta sedeasy riguardante l'uso dell'eco nelle pipe.
Pianosaurus,

0

Questi sono i codici di escape che ho trovato:

* = \x2a
( = \x28
) = \x29

" = \x22
/ = \x2f
\ = \x5c

' = \x27
? = \x3f
% = \x25
^ = \x5e

-1

non dimenticare tutto il piacere che si verifica con la limitazione della shell intorno "e"

quindi (in ksh)

Var=">New version of \"content' here <"
printf "%s" "${Var}" | sed "s/[&\/\\\\*\\"']/\\&/g' | read -r EscVar

echo "Here is your \"text\" to change" | sed "s/text/${EscVar}/g"

esattamente la direzione di cui avevo bisogno, per sfuggire ai risultati di ricerca, trovata tramite google, quindi può essere utile per qualcuno - si è concluso con - sed "s / [& \\\ * \\" \ '\ "') (] / \\ & / g '
MolbOrg,

-1

Se stai solo cercando di sostituire il valore della variabile nel comando sed, rimuovi semplicemente Esempio:

sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test

-2

Se il caso è che stai generando una password casuale da passare per sedsostituire il modello, scegli di fare attenzione a quale set di caratteri nella stringa casuale. Se si sceglie una password creata codificando un valore come base64, allora c'è solo un carattere che è possibile sia in base64 sia un carattere speciale nel sedmodello di sostituzione. Quel carattere è "/" ed è facilmente rimosso dalla password che stai generando:

# password 32 characters log, minus any copies of the "/" character.
pass=`openssl rand -base64 32 | sed -e 's/\///g'`;

-4

Un modo più semplice per farlo è semplicemente costruire la stringa in anticipo e usarla come parametro per sed

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt

Errore ed estremamente pericoloso, poiché REPLACE è fornito dall'utente: REPLACE=/sed: -e expression #1, char 12: unknown option to `s'
Tino il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.