Escapar uma corda para um padrão de substituição sed

317

No meu script bash, tenho uma string externa (recebida do usuário), que devo usar no padrão sed.

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

Como posso escapar da $REPLACEstring para que ela seja aceita com segurança sedcomo uma substituição literal?

NOTA: A KEYWORDé uma substring mudo sem partidas etc. Não é fornecido pelo usuário.

Alexander Gladysh
fonte
13
Você está tentando evitar o problema "Mesas de bobby pequenas" se eles disserem "/ g -e's / PASSWORD =. * / PASSWORD = abc / g '"?
Paul Tomblin
2
Se você estiver usando o bash, não precisará do sed. Basta usaroutputvar="${inputvar//"$txt2replace"/"$txt2replacewith"}".
destenson
@ Densenson Acho que você não deveria colocar as duas variáveis ​​fora das aspas. O Bash pode ler variáveis ​​entre aspas duplas (no seu exemplo, espaço em branco pode estragar tudo).
Camilo Martin
2
Veja também: stackoverflow.com/q/29613304/45375
mklement0
1
@CamiloMartin, veja meu comentário na minha própria resposta. As aspas dentro de $ {} não coincidem com as aspas dentro. As duas variáveis não estão fora das aspas.
destenson 18/08/19

Respostas:

268

Aviso : isso não considera novas linhas. Para uma resposta mais aprofundada, consulte esta pergunta SO . (Obrigado, Ed Morton e Niklas Peter)

Observe que escapar de tudo é uma má ideia. Sed precisa que muitos caracteres sejam escapados para obter seu significado especial. Por exemplo, se você escapar um dígito na sequência de substituição, ela retornará para uma referência anterior.

Como Ben Blank disse, existem apenas três caracteres que precisam ser escapados na sequência de substituição (escapam a si mesmos, barra invertida para final de instrução e & para substituir todos):

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 você precisar escapar da KEYWORDsequência, é o seguinte:

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

E pode ser usado por:

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"

Lembre-se, se você usar um caractere diferente de /delimitador, precisará substituir a barra nas expressões acima pelo caractere que está usando. Veja o comentário de PeterJCLaw para explicação.

Editado: devido a alguns casos de canto anteriormente não contabilizados, os comandos acima foram alterados várias vezes. Verifique o histórico de edições para obter detalhes.

Pianossauro
fonte
17
Vale a pena notar que você pode evitar ter que escapar das barras não usando-as como delimitadores. A maioria das (todas?) Versões do sed permite que você use qualquer caractere, desde que ele se encaixe no padrão: $ echo 'foo / bar' | s sed _ / _: _ # foo: bar
PeterJCLaw
2
sed / s / (\ / \ | \\\ | &) / \\ & / g 'não funcionou para mim no OSX, mas funciona: sed' s / ([\\\ / &]) / \\ & / g 'e é um pouco menor.
Jcoffland #
1
Para a pesquisa-padrão KEYWORD, em GNU sed , aqui estão mais 2 caracteres ^, $não mencionados acima:s/[]\/$*.^|[]/\\&/g
Peter.O
1
@ Jessé: Fixo. De fato, esse é o erro que eu aviso no primeiro parágrafo. Acho que não pratico o que prego.
Pianosaurus
1
@NeronLeVelu: Não sei ao certo o que você quer dizer, mas "não tem significado especial em pipes ou variáveis. Ele é analisado pelo shell antes de executar o resultado, portanto, aspas duplas dentro de variáveis ​​são seguras. Por exemplo, tente executar A='foo"bar' echo $A | sed s/$A/baz/em . bash o aspas são tratados apenas como o 'foo' e 'bar' em torno dele.
Pianosaurus
92

O comando sed permite que você use outros caracteres em vez de /separador:

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

As aspas duplas não são um problema.

scre_www
fonte
5
Você ainda precisa escapar, o .que de outra forma tem um significado especial. Eu editei sua resposta.
ypid
Eu apenas tentei fazer: sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' filecom sed '|CLIENTSCRIPT="foo"|a CLIENTSCRIPT2="hello"' filee isso não faz o mesmo.
Dimitri Kopriwa
1
Como isso se aplica apenas ao substituto, deve-se dizer: O scomando (como substituto) do sed permite que você use outros caracteres em vez de / como um separador. Além disso, isso seria uma resposta para como usar o sed on URL com caracteres de barra. Ele não responde à pergunta do OP como escapar de uma string inserida por um usuário, que pode conter /, \, mas também # se você decidir usá-la. E, além disso, o URI também pode conter #
papo
2
isso mudou minha vida! Obrigado!
Franciscon Santos
48

Os únicos três caracteres literais que são tratados especialmente na cláusula de substituição são /(para fechar a cláusula), \(para escapar dos caracteres, referência anterior, etc.) e &(para incluir a correspondência na substituição). Portanto, tudo que você precisa fazer é escapar desses três caracteres:

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

Exemplo:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar
Ben Blank
fonte
Também uma nova linha, eu acho. Como escapar de uma nova linha?
Alexander Gladysh 02/01/09
2
Cuidado com o comportamento padrão do eco em relação às barras invertidas. No bash, o eco assume como padrão nenhuma interpretação de escapes de barra invertida, que serve ao propósito aqui. No traço (sh), por outro lado, o eco interpreta escapes de barra invertida e, até onde eu sei, não tem como suprimir isso. Portanto, no traço (sh), em vez de eco $ x, imprima '% s \ n' $ x.
Youssef Eldakar 11/12/12
Além disso, sempre use a opção -r ao fazer uma leitura para tratar barras invertidas na entrada do usuário como literais.
Youssef Eldakar
Para compatibilidade de plataforma cruzada com outras conchas, você deve consultar este documento sobre a substituição de caracteres especiais sed: grymoire.com/Unix/Sed.html#toc-uh-62
DeJay Clayton
2
@Drux Os três caracteres são os únicos especiais na cláusula replace . Muito mais é especial na cláusula padrão.
lenz
33

Com base nas expressões regulares do Pianosaurus, criei uma função bash que escapa à palavra-chave e à substituição.

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

Veja como você o usa:

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf
Gurpartap Singh
fonte
3
obrigado! se alguém recebe erro de sintaxe ao tentar usá-lo, assim como eu, lembre-se de executá-lo usando bash, não sh
Konstantin Pereiaslov
1
Existe uma função apenas para escapar de uma string para sed em vez de envolver o sed?
precisa saber é o seguinte
Ei, apenas um aviso geral sobre a inicialização de pipes com um eco como este: Algumas (a maioria?) Implementações de eco aceitam opções (consulte man echo), fazendo com que o pipe se comporte inesperadamente quando seu argumento $1começa com um traço. Em vez disso, você pode iniciar o seu pipe printf '%s\n' "$1".
Pianosaurus 18/03/19
17

É um pouco tarde para responder ... mas há uma maneira muito mais simples de fazer isso. Basta alterar o delimitador (ou seja, o caractere que separa os campos). Então, em vez de s/foo/bar/você escrever s|bar|foo.

E, aqui está a maneira mais fácil de fazer isso:

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

A saída resultante é desprovida dessa desagradável cláusula DEFINER.

user2460464
fonte
10
Não, &e `` ainda deve ser escapado, assim como o delimitador, o que for escolhido.
mirabilos
3
Isso resolveu meu problema, pois eu tinha "/" caracteres em uma string de substituição. Obrigado cara!
Evgeny Goldin
funciona para mim. O que estou fazendo é tentar escapar $na string prestes a ser alterada e manter o significado da $string de substituição. digamos que quero mudar $XXXpara o valor da variável $YYY, sed -i "s|\$XXX|$YYY|g" filefunciona bem.
hakunami
11

Acontece que você está fazendo a pergunta errada. Eu também fiz a pergunta errada. A razão pela qual está errado é o início da primeira frase: "No meu script bash ...".

Eu tive a mesma pergunta e cometi o mesmo erro. Se você estiver usando o bash, não precisará usar o sed para fazer substituições de strings (e é muito mais fácil usar o recurso de substituição incorporado ao bash).

Em vez de algo como, por exemplo:

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

você pode usar os recursos do bash exclusivamente:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"
destenson
fonte
Aliás, a sintaxe destacada aqui está errada. As aspas exteriores coincidem e as aspas interiores coincidem. Em outras palavras, parece $Ae $Bnão está entre aspas, mas não está. As aspas dentro ${}do não coincidem com as aspas fora dele.
destenson
Na verdade, você não precisa citar o lado direito de uma tarefa (a menos que queira fazer algo assim var='has space') - OUTPUT=${INPUT//"$A"/"$B"}é seguro.
Benjamin W.
Na verdade, você não precisa citar o lado direito de uma tarefa (a menos que queira que ela funcione no mundo real e não apenas como um script de brinquedo para mostrar seu talento louco). Eu sempre tento citar cada expansão de variável que não quero que o shell interprete, a menos que eu tenha um motivo específico para não fazê-lo. Dessa forma, as coisas tendem a quebrar com menos frequência, principalmente quando recebidas informações novas ou inesperadas.
usar o seguinte comando
1
Consulte o manual : "Todos os valores passam por expansão de til, expansão de parâmetro e variável, substituição de comando, expansão aritmética e remoção de cotação (detalhado abaixo)." Ou seja, o mesmo que entre aspas duplas.
Benjamin W.
1
E se você precisar usar o sed em um arquivo?
Efren
1

Use awk - é mais limpo:

$ 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
Greggster
fonte
2
O problema awké que ele não tem nada parecido sed -i, o que é extremamente útil 99% do tempo.
Tino
Este é um passo na direção certa, mas o awk ainda interpreta alguns metacaracteres em sua substituição, portanto, ainda não é seguro para a entrada do usuário.
Jeremy Huiskamp 21/01/19
0

Aqui está um exemplo de um AWK que usei há um tempo atrás. É um AWK que imprime novos AWKS. Sendo AWK e SED semelhantes, pode ser um bom modelo.

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

Parece excessivo, mas de alguma forma essa combinação de aspas funciona para manter o 'impresso como literal. Então, se bem me lembro, as variáveis ​​estão rodeadas de aspas como esta: "$ 1". Experimente, deixe-me saber como funciona com o SED.

Alex
fonte
0

Eu tenho uma melhoria sobre a função sedeasy, que quebrará com caracteres especiais como 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"
}

Então, o que é diferente? $1e $2entre aspas para evitar expansões de shell e preservar tabulações ou espaços duplos.

Tubulação adicional | sed -e 's:\t:\\t:g'(eu gosto :como token) que transforma uma guia \t.

Francisco De Zuviria
fonte
Mas veja meu comentário sobre a resposta tranqüila sobre o uso de eco em tubos.
Pianosaurus 18/03/19
0

Estes são os códigos de escape que eu encontrei:

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

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

' = \x27
? = \x3f
% = \x25
^ = \x5e
Ark25
fonte
-1

não se esqueça de todo o prazer que ocorre com a limitação da concha "e '

então (em 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"
NeronLeVelu
fonte
exatamente a direção que eu precisava, para escapar dos resultados encontrados, encontrados no google, pode ser útil para alguém que terminou com - sed "s / [& \\\ * \\" \ '\ "') (] / \\ & / g '
MolbOrg 12/10
-1

Se você está apenas procurando substituir o valor Variável no comando sed, basta remover o Exemplo:

sed -i 's/dev-/dev-$ENV/g' test to sed -i s/dev-/dev-$ENV/g test
Shailender Singh
fonte
-2

Se acontecer de você estar gerando uma senha aleatória para passar para sedsubstituir o padrão, escolha ser cuidadoso sobre qual conjunto de caracteres na sequência aleatória. Se você escolher uma senha criada codificando um valor como base64, haverá apenas um caractere possível na base64 e também um caractere especial no sedpadrão de substituição. Esse caractere é "/" e é facilmente removido da senha que você está gerando:

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

Uma maneira mais fácil de fazer isso é simplesmente construir a string antes da mão e usá-la como parâmetro para sed

rpstring="s/KEYWORD/$REPLACE/g"
sed -i $rpstring  test.txt
Javonne Martin
fonte
Falha e extremamente perigoso, como REPLACE é fornecido pelo usuário: REPLACE=/sed: -e expression #1, char 12: unknown option to `s'
Tino