Reemplace varias cadenas en una sola pasada

Reemplace varias cadenas en una sola pasada

Estoy buscando una manera de reemplazar cadenas de marcadores de posición en un archivo de plantilla con valores concretos, con herramientas comunes de Unix (bash, sed, awk, tal vez perl). Es importante que el reemplazo se haga en una sola pasada, es decir, lo ya escaneado/reemplazado no debe ser considerado para otro reemplazo. Por ejemplo, estos dos intentos fallan:

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

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

El resultado correcto en este caso es, por supuesto, BA.

En general, la solución debería ser equivalente a escanear la entrada de izquierda a derecha en busca de una coincidencia más larga con una de las cadenas de reemplazo dadas y, para cada coincidencia, realizar un reemplazo y continuar desde ese punto en adelante en la entrada (ninguna de las las entradas ya leídas ni las sustituciones realizadas deben considerarse para los partidos). En realidad, los detalles no importan, sólo que los resultados del reemplazo nunca son considerados para otro reemplazo, total o parcial.

NOTASólo busco soluciones genéricas correctas. No proponga soluciones que fallen para determinadas entradas (archivos de entrada, pares de búsqueda y reemplazo), por improbables que parezcan.

Respuesta1

Bien, una solución general. La siguiente función bash requiere 2kargumentos; cada par consta de un marcador de posición y un reemplazo. Depende de usted citar las cadenas adecuadamente para pasarlas a la función. Si el número de argumentos es impar, se agregará un argumento vacío implícito, que eliminará efectivamente las apariciones del último marcador de posición.

Ni los marcadores de posición ni los reemplazos pueden contener caracteres NUL, pero puede usar \escapes C estándar, como \0si necesita NULs (y, en consecuencia, debe escribir \\si desea un \).

Requiere las herramientas de compilación estándar que deberían estar presentes en un sistema similar a Posix (lex y 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"
}

Suponemos que \ya se ha escapado si es necesario en los argumentos, pero debemos evitar las comillas dobles, si están presentes. Eso es lo que hace el segundo argumento del segundo printf. Dado que la lexacción predeterminada es ECHO, no necesitamos preocuparnos por eso.

Ejecución de ejemplo (con tiempos para los escépticos; es solo una computadora portátil barata):

$ 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 más grandes, podría ser útil proporcionar un indicador de optimización ccy, para la compatibilidad actual con Posix, sería mejor usar c99. Una implementación aún más ambiciosa podría intentar almacenar en caché los ejecutables generados en lugar de generarlos cada vez, pero no son precisamente costosos de generar.

Editar

Si usted tienetcc, puede evitar la molestia de crear un directorio temporal y disfrutar de un tiempo de compilación más rápido que ayudará en entradas de tamaño 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

Respuesta2

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 como esto siempre reemplazará cada aparición de sus cadenas de destino solo una vez, ya que ocurren ensed la secuencia de un bocado por línea. Esta es la forma más rápida que puedo imaginar que lo harías. Por otra parte, no escribo C. Pero estohacemaneje de manera confiable delimitadores nulos si así lo desea. Veresta respuestapor cómo funciona. Esto no tiene problemas con los caracteres de shell especiales contenidos o similares, peroesASCII es específico de la configuración regional o, en otras palabras, odno generará caracteres de varios bytes en la misma línea y solo generará uno por cada. Si esto es un problema, querrás agregar iconv.

Respuesta3

Una perlsolución. Incluso si algunos dijeron que no es posible, encontré uno, pero en general una simple coincidencia y reemplazo no es posible e incluso empeora debido al retroceso de una NFA, el resultado puede ser inesperado.

En general, y hay que decirlo, el problema arroja diferentes resultados que dependen del orden y longitud de las tuplas de reemplazo. es decir:

A B
AA CC

y la entrada AAAda como resultado BBBo CCB.

Aquí el 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

Conejito de damas:

$ echo 'ABBabbaBBbaabAAabbc'|perl script
$ BAAbaabAAabbcBBaaba

información relacionada