Existe uma maneira de evitar que o sed interprete a string de substituição? [fechadas]

4

Se você deseja substituir uma palavra-chave por uma string usando sed, o sed tenta interpretar sua string substituta. Se a string substituta tiver caracteres considerados especiais, como um caractere '/', ela falhará, a não ser, é claro, que você queira que sua string substituta tenha caracteres que digam ao sed como agir.

Ex:

VAR="hi/"

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

Existe alguma maneira de dizer ao sed para não tentar interpretar a string de substituição para caracteres especiais? Tudo que eu quero é poder substituir uma palavra-chave em um arquivo com o conteúdo de uma variável, não importando o conteúdo.

    
por Tal 17.01.2016 / 01:28

7 respostas

4

Você pode usar Perl em vez de sed com -p (suponha loop de entrada) e -e (dê programa na linha de comando). Com o Perl, você pode acessar variáveis de ambiente sem interpolando-as no shell. Observe que a variável precisa ser exportada :

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

Se você não quiser exportar a variável em todos os lugares, basta fornecê-la apenas para esse processo:

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

Observe que a sintaxe de expressão regular do Perl é, por padrão, um pouco diferente da do sed.

    
por 17.01.2016 / 11:28
2

A solução mais simples que ainda manuseia a grande maioria dos valores de variáveis corretamente, seria usar um caractere não-imprimível como um delimitador para o comando substituto de sed .

Em vi você pode escapar de qualquer caractere de controle digitando Ctrl-V (mais comumente escrito como ^V ). Portanto, se você usar algum caractere de controle (geralmente uso ^A como um delimitador nesses casos), seu comando sed só será interrompido se esse caractere não imprimível estiver presente na variável que você está inserindo.

Então, você digitaria "s^V^AKEYWORD^V^A$VAR^V^Ag" e o que receberia (em vi ) seria:

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

Isso funcionará, desde que $VAR não contenha o caractere não imprimível ^A , o que é extremamente improvável.

É claro que, se você passar entrada do usuário para o valor de $VAR , todas as apostas serão desativadas e será melhor limpar completamente suas entradas, em vez de confiar em caracteres de controle difíceis para digitar para o usuário médio.

Na verdade, há mais a se precaver do que a string delimitadora. Por exemplo, & , quando presente em uma string de substituição, significa "todo o texto que foi correspondido". Por exemplo, s/stu../my&/ iria substituir "coisas" por "mystuff", "stung" por "mystung", etc. Então, se você pode ter qualquer caractere na variável que você está inserindo como um Se você quiser usar o valor literal da variável apenas, terá alguns dados sanitizantes antes de usar a variável como uma string de substituição em sed . (O saneamento de dados pode ser feito com sed também.)

    
por 17.01.2016 / 08:29
1

Você pode usar um , ou um | e ele será usado como um separador e tecnicamente você poderia usar qualquer coisa

da página de manual

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

Como você pode ver, você deve começar com um \ antes de seu separador no começo, então você pode usá-lo como um separador.

da documentação link :

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.

Exemplo:

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

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

    
por 17.01.2016 / 01:34
1

Se for uma linha e apenas uma linha para substituir, recomendo que você prefixe o arquivo com a linha de substituição usando printf , armazenando a primeira linha no espaço de retenção de sed e soltando-a conforme necessário. Dessa forma, você não precisa se preocupar com caracteres especiais. (A única suposição aqui é que $VAR contém uma única linha de texto sem nenhuma nova linha, que é o que você disse nos comentários.) Além de novas linhas, o VAR poderia conter qualquer coisa e isso trabalhar independentemente.

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

printf '%s\n' imprimirá o conteúdo de $VAR como uma string literal, independentemente de seu conteúdo, seguido por uma nova linha. ( echo fará outras coisas em alguns casos, por exemplo, se o conteúdo de $VAR começar com um hífen - ele será interpretado como um sinalizador de opção sendo passado para echo .)

As chaves são usadas para pré-adicionar a saída de printf ao conteúdo de somefile à medida que é passado para sed . O espaço em branco que separa as chaves por si só é importante aqui, assim como o ponto-e-vírgula antes da chave de fechamento.

1{h;d;}; como um comando sed armazenará a primeira linha do texto no espaço hold de sed , então d elete a linha (ao invés de imprimi-lo). / p>

/KEYWORD/ aplica as seguintes ações a todas as linhas que contêm KEYWORD . A ação é g et, que obtém o conteúdo do espaço de armazenamento e o coloca no lugar do espaço padrão - em outras palavras, toda a linha atual. (Isto não é para substituir apenas parte de uma linha.) A propósito, o espaço de armazenamento não é esvaziado, apenas copiado para o espaço padrão, substituindo qualquer está lá.

Se você quiser ancorar seu regex, ele não corresponderá a uma linha que apenas contém KEYWORD, mas apenas uma linha em que não há mais nada na linha, exceto KEYWORD , adicione um início de âncora de linha ( ^ ) e âncora de fim de linha ( $ ) ao seu regex:

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

Você pode excluir a barra invertida das barras em sua sequência de substituição, usando a expansão de parâmetro de substituição de padrão do Bash. É um pouco confuso, porque as barras também precisam ser escapadas para o Bash.

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

saída

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

Você poderia colocar a expansão de parâmetro diretamente no seu comando sed:

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

mas acho que a primeira forma é um pouco mais legível. E, claro, se você for reutilizar o mesmo padrão de substituição em vários comandos sed, faz sentido apenas fazer a conversão uma vez.

Outra opção seria usar um script escrito em awk, perl ou Python, ou um programa C, para fazer suas substituições ao invés de usar sed.

Aqui está um exemplo simples em Python que funciona se a palavra-chave a ser substituída for uma linha completa no arquivo de entrada (sem contar a nova linha). Como você pode ver, é basicamente o mesmo algoritmo do seu exemplo de Bash, mas lê o arquivo de entrada com mais eficiência.

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)
    
por 17.01.2016 / 08:02
0

Existem apenas 4 caracteres especiais na parte de substituição: \ , & , nova linha e o delimitador ( 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
    
por 17.01.2016 / 13:30
-1

É assim que eu fui:

#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
}

isso funciona muito bem no meu caso porque minha palavra-chave está em uma linha sozinha. Se a palavra-chave estivesse em linha com outro texto, isso não funcionaria.

Eu ainda gostaria de saber se existe uma maneira fácil de fazer isso que não envolve codificar minha própria solução.

    
por 17.01.2016 / 09:38