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 2k
argumentos; 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 \0
se precisar NUL
de 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 lex
açã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 cc
e, 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 sed
fluxo, 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, od
não produzirá caracteres multibyte na mesma linha e fará apenas um por. Se isso for um problema, você desejará adicionar iconv
.
Responder3
Uma perl
soluçã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 AAA
resulta em BBB
ou 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