Substitua várias strings em uma única passagem

Substitua várias strings em uma única passagem

Estou procurando uma maneira de substituir strings de espaço reservado em um arquivo de modelo por valores concretos, com ferramentas Unix comuns (bash, sed, awk, talvez perl). É importante que a substituição seja feita em uma única passagem, ou seja, o que já foi digitalizado/substituído não deve ser considerado para outra substituição. Por exemplo, estas duas tentativas falham:

echo "AB" | awk '{gsub("A","B");gsub("B","A");print}'
>> AA

echo "AB" | sed 's/A/B/g;s/B/A/g'
>> AA

O resultado correto neste caso é obviamente BA.

Em geral, a solução deve ser equivalente a varrer a entrada da esquerda para a direita em busca de uma correspondência mais longa para uma das strings de substituição fornecidas e, para cada correspondência, realizar uma substituição e continuar daquele ponto em diante na entrada (nenhuma das a entrada já lida nem as substituições realizadas devem ser consideradas para correspondências). Na verdade, os detalhes não importam, apenas que os resultados da substituição nunca são considerados para outra substituição, no todo ou em parte.

OBSERVAÇÃOEstou apenas procurando soluções genéricas corretas. Por favor, não proponha soluções que falhem para determinadas entradas (arquivos de entrada, pesquisa e substituição de pares), por mais improváveis ​​que possam parecer.

Responder1

OK, uma solução geral. A seguinte função bash requer 2kargumentos; cada par consiste em um espaço reservado e um substituto. Cabe a você citar as strings adequadamente para passá-las para a função. Se o número de argumentos for ímpar, um argumento vazio implícito será adicionado, o que excluirá efetivamente as ocorrências do último espaço reservado.

Nem os espaços reservados nem as substituições podem conter caracteres NUL, mas você pode usar \escapes C padrão, como \0se precisar NULde s (e, conseqüentemente, será necessário escrever \\se desejar a \).

Requer as ferramentas de construção padrão que devem estar presentes em um sistema tipo posix (lex e cc).

replaceholder() {
  local dir=$(mktemp -d)
  ( cd "$dir"
    { printf %s\\n "%option 8bit noyywrap nounput" "%%"
      printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
      printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
    } | lex && cc lex.yy.c
  ) && "$dir"/a.out
  rm -fR "$dir"
}

Assumimos que \já existe escape, se necessário, nos argumentos, mas precisamos escapar de aspas duplas, se presentes. É isso que o segundo argumento do segundo printf faz. Como a lexação padrão é ECHO, não precisamos nos preocupar com isso.

Exemplo de execução (com horários para os céticos; é apenas um laptop barato):

$ time echo AB | replaceholder A B B A
BA

real    0m0.128s
user    0m0.106s
sys     0m0.042s
$ time printf %s\\n AB{0000..9999} | replaceholder A B B A > /dev/null

real    0m0.118s
user    0m0.117s
sys     0m0.043s

Para entradas maiores, pode ser útil fornecer um sinalizador de otimização cce, para compatibilidade atual com Posix, seria melhor usar c99. Uma implementação ainda mais ambiciosa poderia tentar armazenar em cache os executáveis ​​gerados em vez de gerá-los todas as vezes, mas sua geração não é exatamente cara.

Editar

Se você temtcc, você pode evitar o incômodo de criar um diretório temporário e aproveitar o tempo de compilação mais rápido, que ajudará em entradas de tamanho normal:

treplaceholder () { 
  tcc -run <(
  {
    printf %s\\n "%option 8bit noyywrap nounput" "%%"
    printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
    printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
  } | lex -t)
}

$ time printf %s\\n AB{0000..9999} | treplaceholder A B B A > /dev/null

real    0m0.039s
user    0m0.041s
sys     0m0.031s

Responder2

printf 'STRING1STRING1\n\nSTRING2STRING1\nSTRING2\n' |
od -A n -t c -v -w1 |
sed 's/ \{1,3\}//;s/\\$/&&/;H;s/.*//;x
     /\nS\nT\nR\nI\nN\nG\n1/s//STRING2/
     /\nS\nT\nR\nI\nN\nG\n2/s//STRING1/
     /\\n/!{x;d};s/\n//g;s/./\\&/g' |
     xargs printf %b

###OUTPUT###

STRING2STRING2

STRING1STRING2
STRING1

Algo assim sempre substituirá cada ocorrência de suas strings de destino apenas uma vez, à medida que ocorrerem no sedfluxo, com uma mordida por linha. Esta é a maneira mais rápida que posso imaginar de que você faria isso. Então, novamente, eu não escrevo C. Mas issofazlidar de forma confiável com delimitadores nulos, se desejar. Veresta respostapara saber como funciona. Isto não tem problemas com quaisquer caracteres shell especiais contidos ou similares - maséEspecífico do local ASCII ou, em outras palavras, odnão produzirá caracteres multibyte na mesma linha e fará apenas um por. Se isso for um problema, você desejará adicionar iconv.

Responder3

Uma perlsolução. Mesmo que alguns afirmem que não é possível, encontrei um, mas em geral uma simples correspondência e substituição não é possível e mesmo piorando por causa do retrocesso de um NFA o resultado pode ser inesperado.

Em geral, e isto deve ser dito, o problema produz resultados diferentes que dependem da ordem e do comprimento das tuplas de substituição. ou seja:

A B
AA CC

e a entrada AAAresulta em BBBou CCB.

Aqui o código:

#!/usr/bin/perl

$v='if (0) {} ';
while (($a,$b)=split /\s+/, <DATA>) {
  $k.=$a.'|';
  $v.='elsif ($& eq \''.$a.'\') {print \''.$b.'\'} ';
}
$k.='.';
$v.='else {print $&;}';

eval "
while (<>) {
  \$_ =~ s/($k)/{$v}/geco;
}";  
print "\n";


__DATA__
A    B
B    A
abba baab
baab abbc
abbc aaba

Coelhinho Xadrez:

$ echo 'ABBabbaBBbaabAAabbc'|perl script
$ BAAbaabAAabbcBBaaba

informação relacionada