C'è un modo per impedire a sed di interpretare la stringa di sostituzione? [chiuso]


16

Se si desidera sostituire una parola chiave con una stringa utilizzando sed, sed si impegna a fondo per interpretare la stringa di sostituzione. Se la stringa sostitutiva sembra avere caratteri che sed considera speciali, come un carattere "/", fallirà, a meno che, naturalmente, tu abbia inteso che la stringa sostitutiva abbia caratteri che dicono a sed come agire.

Ex:

VAR="hi/"

sed "s/KEYWORD/$VAR/g" somefile

C'è un modo per dire a sed di non provare a interpretare la stringa di sostituzione per caratteri speciali? Tutto quello che voglio è essere in grado di sostituire una parola chiave in un file con il contenuto di una variabile, indipendentemente dal contenuto.


Se vuoi inserire personaggi speciali sede farli non essere speciali, basta sfuggirli. VAR='hi\/'non dà questo problema.
Wildcard il

6
Perché tutti i downvotes? Mi sembra una domanda perfettamente ragionevole
roaima,

sed(1)interpreta solo ciò che ottiene. Nel tuo caso, lo ottiene tramite un'interpolazione shell. Credo che non puoi fare quello che vuoi, ma controlla il manuale. So che in Perl (che rende una sedsostituzione accettabile , con espressioni regolari molto più ricche) puoi specificare che una stringa deve essere presa letteralmente, di nuovo, controlla il manuale.
vonbrand,

Risposte:


4

Ci sono solo 4 caratteri speciali nel pezzo di ricambio: \, &, a capo e il delimitatore ( ref )

$ VAR='abc/def&ghi\foo
next line'

$ repl=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$VAR")

$ echo "$repl"
abc\/def\&ghi\\foo\
next line

$ echo ZYX | sed "s/Y/$repl/g"
Zabc/def&ghi\foo
next lineX

Questo ha lo stesso problema della soluzione Antti: se la stringa di sostituzione supera una certa lunghezza, viene visualizzato l'errore "Elenco argomenti troppo lungo". Inoltre, cosa succede se la stringa di sostituzione ha '[', ']', '*', '.' E altri caratteri simili? Sed non li interpreterebbe davvero?
Tal,

Il lato sostitutivo di nons/// è un'espressione regolare, è in realtà solo una stringa (ad eccezione di backslash-escape e ). Se la stringa di sostituzione è così lunga, la soluzione a un liner shell non è la soluzione. &
Glenn Jackman,

Un elenco molto utile se, ad esempio, la stringa di sostituzione è un testo codificato in base64 (ad es. Sostituzione di un segnaposto con una chiave SHA256). Quindi è solo il delimitatore di cui preoccuparsi.
Heath Raftery,

4

Puoi usare Perl invece di sed con -p(assume loop over input) e -e(dai programma alla riga di comando). Con Perl è possibile accedere alle variabili di ambiente senza interpolarle nella shell. Nota che la variabile deve essere esportata :

export VAR='hi/'
perl -p -e 's/KEYWORD/$ENV{VAR}/g' somefile

Se non si desidera esportare la variabile ovunque, fornirla solo per quel processo:

PATTERN="$VAR" perl -p -e 's/KEYWORD/$ENV{PATTERN}/g' somefile

Si noti che la sintassi delle espressioni regolari di Perl è di default leggermente diversa da quella di sed.


Questo mi è sembrato molto promettente, ma quando lo collaudo, ricevo un errore "Elenco argomenti troppo lungo" perché la mia stringa di sostituzione è troppo lunga, il che ha senso - usando questo metodo, stiamo usando l'intera stringa di sostituzione come parte degli argomenti che forniamo perl, quindi c'è un limite a quanto può durare.
Tal

1
No, andrà nella PATTERN variabile d'ambiente , non negli argomenti. In ogni caso, questo errore sarebbe E2BIG, che otterresti ugualmente se lo usassi sed.
Antti Haapala,

2

La soluzione più semplice che gestisca ancora correttamente la stragrande maggioranza dei valori delle variabili sarebbe quella di utilizzare un carattere non stampabile come delimitatore al sedcomando sostitutivo.

In vipuoi sfuggire a qualsiasi personaggio di controllo digitando Ctrl-V (più comunemente scritto come ^V). Quindi se usi un carattere di controllo ( ^Ain questi casi lo uso spesso come delimitatore), il tuo sedcomando si interromperà solo se quel carattere non stampabile è presente nella variabile in cui ti stai inserendo.

Quindi digitare "s^V^AKEYWORD^V^A$VAR^V^Ag"e vicome apparirà (in ):

sed "s^AKEYWORD^A$VAR^Ag" somefile

Funzionerà finché $VARnon contiene il carattere non stampabile, il ^Ache è estremamente improbabile.


Ovviamente, se stai passando l'input dell'utente nel valore di $VAR, allora tutte le scommesse sono disattivate e faresti meglio a disinfettare completamente l'input piuttosto che affidarti a caratteri di controllo difficili da digitare per l'utente medio.


Tuttavia, in realtà c'è molto più da fare attenzione rispetto alla stringa del delimitatore. Ad esempio, &se presente in una stringa di sostituzione, significa "l'intero testo che è stato abbinato". Ad esempio, s/stu../my&/sostituirebbe "stuff" con "mystuff", "stung" con "mystung", ecc. Quindi, se si potrebbe avere un carattere nella variabile che si sta inserendo come stringa sostitutiva, ma si desidera utilizzare il valore letterale solo il valore della variabile, quindi è necessario eseguire alcune operazioni di disinfezione dei dati prima di poter utilizzare la variabile come stringa di sostituzione in sed. (Tuttavia, la disinfezione dei dati può essere eseguita sedanche con .)


Questo è il mio punto: sostituire una stringa con un'altra stringa è un'operazione molto semplice. Deve davvero essere complicato come capire quali personaggi non piaceranno a sed e usare sed per disinfettare il proprio input? Sembra ridicolmente e inutilmente contorto. Non sono un programmatore professionista, ma sono abbastanza sicuro di poter codificare una piccola funzione che sostituisce una parola chiave con una stringa in quasi tutte le lingue che abbia mai incontrato, incluso bash - Speravo solo in un semplice Linux soluzione usando gli strumenti esistenti - non posso credere che non ce ne sia uno là fuori.
Tal,

1
@Tal, se la tua stringa di sostituzione è lunga "100s di pagine" come dici in un altro commento ... difficilmente puoi chiamarla un "semplice" caso d'uso. La risposta qui è Perl, a proposito: non ho ancora imparato Perl. La complessità qui deriva dal fatto che si desidera consentire QUALSIASI input arbitrario come stringa sostitutiva in una regex .
Wildcard il

Esistono numerose altre soluzioni che potresti utilizzare, molte delle quali molto semplici. Ad esempio, se la stringa di sostituzione è effettivamente basata su riga e non deve essere inserita nel mezzo di una riga, utilizzare sedil icomando nsert. Ma sednon è un buon strumento per elaborare grandi quantità di testo in modi complessi. Pubblicherò un'altra risposta che mostra come farlo awk.
Wildcard il

1

Puoi usare a ,o a |invece e lo prenderai come un separatore e tecnicamente potresti usare qualsiasi cosa

dalla pagina man

\cregexpc
           Match lines matching the regular expression regexp.  The  c  may
      be any character.

Come puoi vedere, dovresti iniziare con un \ prima del tuo separatore all'inizio, quindi puoi usarlo come separatore.

dalla documentazione http://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command :

The / characters may be uniformly replaced by any other single character 
within any given s command.

The / character (or whatever other character is used in its stead) can appear in 
the regexp or replacement only if it is preceded by a \ character.

Esempio:

sed -e 'somevar|s|foo|bar|'
echo "Hello all" | sed "s_all_user_"
echo "Hello all" | sed "s,all,user,"

echo "Hello/ World" | sed "s,Hello/,Neo,"


Stai parlando di consentire l'uso di un singolo carattere specifico nella stringa di sostituzione, in questo caso "/". Sto parlando di impedirgli di provare a interpretare del tutto la stringa di sostituzione. Indipendentemente dal carattere che usi ("/", ",", "|", ecc.) Rischi sempre di far apparire quel carattere nella stringa di sostituzione. Inoltre, il personaggio iniziale non è l'unico personaggio speciale a cui si preoccupa sed, vero?
Tal,

@Tal no può prendere qualsiasi cosa invece di /e ignorerà /felicemente come ho appena sottolineato .. in effetti, puoi persino cercarlo e sostituirlo in una stringa >>> ho modificato con un esempio >>> questi cose non sono così sicure e troverai sempre un tipo più intelligente
user3566929

@Tal perché vuoi impedirgli di interpretare? voglio dire che è l'uso sedin primo luogo, qual è il tuo progetto?
user3566929

Tutto ciò che serve è sostituire una parola chiave con una stringa. sed sembra essere il modo più comune, di gran lunga, per farlo in Linux. La stringa può essere lunga 100 pagine. Non voglio provare a disinfettare la stringa in modo che sed non impazzisca durante la lettura - voglio che sia in grado di gestire qualsiasi carattere della stringa e, "maneggiando", intendo non cercare di trovare magico significato dentro.
Tal

1
@Tal, NONbash è per la manipolazione di stringhe. Assolutamente no. È per la manipolazione dei file e il coordinamento dei comandi . Capita di avere alcune funzionalità integrate utili per le stringhe, ma davvero limitate e non molto veloci se questa è la cosa principale che stai facendo. Vedi "Perché usare un loop di shell per elaborare il testo è considerato una cattiva pratica?" Alcuni strumenti che sono progettati per l'elaborazione del testo sono, in ordine dal più semplice al più potente: , e Perl. sedawk
Wildcard il

1

Se è basato su una riga e solo una riga da sostituire, ti consiglio di anteporre il file stesso alla riga di sostituzione utilizzando printf, memorizzando quella prima riga nello sedspazio di attesa e inserendola secondo necessità. In questo modo non devi preoccuparti di personaggi speciali. (L'unica ipotesi qui è che $VARcontenga una sola riga di testo senza nessuna nuova riga, che è già ciò che hai detto nei commenti.) Oltre alle nuove righe, VAR potrebbe contenere qualsiasi cosa e questo funzionerebbe a prescindere.

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/KEYWORD/g'

printf '%s\n'stamperà il contenuto $VARcome una stringa letterale, indipendentemente dal suo contenuto, seguito da una nuova riga. ( echofarà altre cose in alcuni casi, ad esempio se il contenuto di $VARinizia con un trattino, verrà interpretato come un flag di opzione a cui viene passato echo).

Le parentesi graffe vengono utilizzate per anteporre l'output printfal contenuto di somefilequando viene passato sed. Qui è importante lo spazio bianco che separa le parentesi graffe, così come il punto e virgola prima della parentesi graffa di chiusura.

1{h;d;};come un sedcomando memorizzare la prima riga di testo in sed's spazio attesa , quindi delete la linea (piuttosto che stamparlo).

/KEYWORD/applica le seguenti azioni a tutte le righe che contengono KEYWORD. L'azione è get, che ottiene il contenuto dello spazio di attesa e lo rilascia al posto dello spazio del modello, in altre parole, l'intera riga corrente. (Questo non è per sostituire solo una parte di una linea.) Lo spazio di mantenimento non viene svuotato, comunque, semplicemente copiato nello spazio del modello, sostituendo qualunque cosa ci sia.

Se vuoi ancorare il tuo regex in modo che non corrisponda a una linea che contiene semplicemente KEYWORD ma solo una linea dove non c'è nient'altro sulla linea tranne KEYWORD, aggiungi un inizio di ancoraggio di linea ( ^) e fine di ancoraggio di linea ( $) a la tua regex:

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/^KEYWORD$/g'

Sembra fantastico se il tuo VAR è lungo una riga. Nei commenti ho effettivamente detto che VAR "può essere lungo 100 pagine" anziché una riga. Dispiace per la confusione.
Tal

0

È possibile eseguire il backslash-escape delle barre in avanti nella stringa di sostituzione, utilizzando l'espansione del parametro di sostituzione del modello di Bash. È un po 'disordinato perché anche le barre in avanti devono essere sfuggite a Bash.

$ var='a/b/c';var="${var//\//\\/}";echo 'this is a test' | sed "s/i/$var/g"

produzione

tha/b/cs a/b/cs a test

È possibile inserire l'espansione dei parametri direttamente nel comando sed:

$ var='a/b/c';echo 'this is a test' | sed "s/i/${var//\//\\/}/g"

ma penso che il primo modulo sia un po 'più leggibile. E ovviamente se riutilizzerai lo stesso schema di sostituzione in più comandi sed, è logico fare la conversione una sola volta.

Un'altra opzione sarebbe quella di usare uno script scritto in awk, perl o Python o un programma C, per fare le tue sostituzioni invece di usare sed.


Ecco un semplice esempio in Python che funziona se la parola chiave da sostituire è una riga completa nel file di input (senza contare la nuova riga). Come puoi vedere, è essenzialmente lo stesso algoritmo del tuo esempio di Bash, ma legge il file di input in modo più efficiente.

import sys

#Get the keyword and replacement texts from the command line
keyword, replacement = sys.argv[1:]
for line in sys.stdin:
    #Strip any trailing whitespace
    line = line.rstrip()
    if line == keyword:
        line = replacement
    print(line)

Questo è solo un altro modo per disinfettare l'input, e non eccezionale, poiché gestisce solo un carattere specifico ('/'). Come sottolineato da Wildcard, c'è molto di più da fare attenzione oltre alla stringa delimitatore.
Tal

Chiamata giusta. Ad esempio, se il testo sostitutivo contiene sequenze con escape di barra rovesciata, verranno interpretate, il che potrebbe non essere desiderabile. Un modo per aggirare questo sarebbe convertire i caratteri problematici (o il tutto) in \xsequenze di escape in stile. O per usare un programma in grado di gestire input arbitrari, come ho detto nel mio ultimo paragrafo.
PM 2Ring

@Tal: aggiungerò un semplice esempio di Python alla mia risposta.
PM 2Ring

Lo script Python funziona alla grande e sembra fare esattamente quello che fa la mia funzione, solo in modo molto più efficiente. Sfortunatamente, se lo script principale è bash (come nel mio caso), questo richiede l'uso di uno script Python esterno secondario.
Tal,

-1

È andata così:

#Replaces a keyword with a long string
#
#This is normally done with sed, but sed
#tries to interpret the string you are
#replacing the keyword with too hard
#
#stdin - contents to look through
#Arg 1 - keyword to replace
#Arg 2 - what to replace keyword with
replace() {
        KEYWORD="$1"
        REPLACEMENT_STRING="$2"

        while IFS= read -r LINE
        do
                if [[ "$LINE" == "$KEYWORD" ]]
                then
                        printf "%s\n" "$REPLACEMENT_STRING"
                else
                        printf "%s\n" "$LINE"
                fi
        done < /dev/stdin
}

questo funziona alla grande nel mio caso perché la mia parola chiave è su una riga da sola. Se la parola chiave fosse in linea con altro testo, ciò non funzionerebbe.

Mi piacerebbe davvero sapere se esiste un modo semplice per farlo che non comporta la codifica della mia soluzione.


1
Se sei davvero preoccupato per i caratteri speciali e la robustezza, non dovresti usare echoaffatto. Usa printfinvece. E l' elaborazione del testo in un ciclo di shell è una cattiva idea.
Wildcard il

1
Sarebbe stato utile se hai indicato nella domanda che la parola chiave sarà sempre una riga completa. FWIW, bash's readè piuttosto lento. È pensato per l'elaborazione dell'input interattivo dell'utente, non per l'elaborazione di file di testo. È lento perché legge il carattere stdin per carattere, facendo una chiamata di sistema per ogni carattere.
PM 2Ring

@PM 2Ring La mia domanda non ha menzionato che la parola chiave si trova su una riga a parte perché non voglio una risposta che funzioni solo in un numero così limitato di casi: volevo qualcosa che potesse facilmente funzionare indipendentemente da dove la parola chiave era. Inoltre non ho mai detto che il mio codice fosse efficiente - se lo fosse, non avrei cercato un'alternativa ...
Tal

@Wildcard A meno che non mi manchi qualcosa, printf interpreta assolutamente caratteri speciali, e molto di più rispetto all'eco predefinito. printf "hi\n"renderà printf print una nuova riga mentre la echo "hi\n"stampa così com'è.
Tal,

@Tal, la "f" printfsta per "formato", il primo argomento printfè un identificatore di formato . Se lo specificatore è %s\n, che significa "stringa seguita da una nuova riga", nulla nel prossimo argomento verrà interpretato o tradotto printf affatto . (La shell può ancora interpretarlo, ovviamente; meglio attaccarlo tutto tra virgolette singole se è una stringa letterale, o virgolette doppie se si desidera l'espansione variabile.) Vedere la mia risposta usandoprintf per maggiori dettagli.
Wildcard 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.