Substitui a primeira ocorrência de um padrão em um arquivo que pode conter uma barra

5

Graças a este link sei como para passar uma variável que contém barras como padrão para sed:

sed "s~$var~replace~g" $file . Juste usa um caractere de byte único no lugar de /.

Obrigado a this outro link Eu também sei como substituir apenas a primeira ocorrência de um padrão em um arquivo (não em uma linha):

sed "0,/$var/s/$var/replacement/" filename ou sed 0,/$var/{s/$var/replacement/} filename

Mas se eu fizer: %código% (ou qualquer outra coisa que comece com 0 e, em seguida, sem barra), recebi um erro: sed '0,~$var~s~$var~replacement~' filename .

Como eu poderia combinar os dois? Talvez usando awk ou perl ou ...?

    
por ratnoz 20.07.2016 / 16:11

2 respostas

9

Enquanto:

sed "0,\~$var~s~$var~replacement~"

Pode ser usado para alterar o delimitador regex, incorporando expansões variáveis dentro do código sed (ou qualquer outro interpretador) é uma coisa muito insensata no caso geral.

Primeiro, aqui, o delimitador não é o único caractere que precisa ser escapado. Todos os operadores de expressões regulares também precisam.

Mas o mais importante, e especialmente com o GNU sed , é uma vulnerabilidade de injeção de comando. Se o conteúdo de $var não estiver sob seu controle, é tão ruim quanto passar dados arbitrários para eval .

Tente por exemplo:

$ var='^~s/.*/uname/e;#'
$ echo | sed "0,\~$var~s~$var~replacement~"
Linux

O comando uname foi executado, felizmente inofensivo ... desta vez.

As implementações não-GNU sed não podem executar comandos arbitrários, mas podem sobrescrever qualquer arquivo (com o comando w ), que é virtualmente tão ruim.

Uma maneira mais correta é escapar dos caracteres problemáticos em $var first :

NL='
'
case $var in
  (*"$NL"*)
    echo >&2 "Sorry, can't handle variables with newline characters"
    exit 1
esac
escaped_var=$(printf '%s\n' "$var" | sed 's:[][\/.^$*]:\&:g')
# and then:
sed "0,/$escaped_var/s/$escaped_var/replacement/" < file

Outra abordagem é usar perl :

var=$var perl -pe 's/\Q$ENV{var}\E/replacement/g && $n++ unless $n' < file

Observe que não estamos expandindo o conteúdo de $var dentro do código passado para perl (que seria outra vulnerabilidade de injeção de comando), mas deixando perl expandir seu conteúdo como parte de seu processamento de regexp ( e dentro de \Q...\E , o que significa que os operadores regexp não são tratados especialmente).

Se $var contiver caracteres de nova linha, isso só poderá coincidir se houver apenas um no final. Como alternativa, pode-se passar a opção -0777 para que a entrada seja processada como um registro único em vez de linha por linha.

    
por 20.07.2016 / 16:58
1

Semelhante à solução perl de Stephane, awk pode evitar a questão de proteger um valor interpolado "perigoso" com uma abordagem alternativa:

export pattern="anypattern" # or whatever shell quoting is needed
awk 'NR==1,$0~ENVIRON["pattern"] {sub(ENVIRON["pattern"],"replacement")} 1' input
# or gsub if you want multiple matches on the first line with any match

e se os dados forem (possivelmente) grandes e o padrão (possivelmente) custoso, awk também poderá evitar correspondência desnecessária depois que uma correspondência for encontrada com um idioma ligeiramente diferente:

awk '!x&&$0~ENVIRON["pattern"] {sub(ENVIRON["pattern"],"replacement");x=1} 1' input
# ditto

Lembrete: awk (ou sed ) trata & no valor de substituição especialmente; se você precisar de um prefixo & real com \ (que geralmente deve ser primeiro citado pelo shell; aqui está em '' ) e, da mesma forma, dobrar um% real\

    
por 22.07.2016 / 08:59