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 2k
argumentos; 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 \0
si necesita NUL
s (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 lex
acció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 cc
y, 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, od
no 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 perl
solució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 AAA
da como resultado BBB
o 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