2011-08-25 12 views
10

Supongamos que tengo dos listas de cadenas (lista A y lista B) con el mismo número exacto de entradas, N, en cada lista, y deseo reemplazar todas las instancias del elemento n-ésimo de A con el enésimo elemento de B en un archivo en Unix (lo ideal sería usar Bash scripting).¿Cuál es una forma eficiente de reemplazar la lista de cadenas con otra lista en el archivo Unix?

¿Cuál es la forma más eficiente de hacer esto?

Una manera ineficiente sería hacer N llamadas a "sed s/stringA/stringB/g".

Respuesta

9

Esto lo hará en una sola pasada. Lee listA y listB en arrays awk, luego, para cada línea del linput, examina cada palabra y si la palabra se encuentra en listA, la palabra es reemplazada por la palabra correspondiente en listB.

awk ' 
    FILENAME == ARGV[1] { listA[$1] = FNR; next } 
    FILENAME == ARGV[2] { listB[FNR] = $1; next } 
    { 
     for (i = 1; i <= NF; i++) { 
      if ($i in listA) { 
       $i = listB[listA[$i]] 
      } 
     } 
     print 
    } 
' listA listB filename > filename.new 
mv filename.new filename 

Asumo las cadenas de listaA no contienen espacios en blanco (separador de campo predeterminado de awk)

+1

Agradable, en general, pero con un problema potencial. Esta solución no preserva necesariamente el espaciado entre palabras en las líneas donde se realizan los cambios; las corridas de espacios en blanco se cambian a espacios individuales. Como no conocemos la naturaleza del texto, podría no ser un problema e incluso podría ser una ventaja. De todos modos, +1 de mi parte. –

+0

Esto es claramente más eficiente que la solución a continuación que escribe un script sed. Terminado en 3 minutos, lo que tomó 3 días con la solución sed. También reemplaza solo palabras completas, aunque esto no es lo que se pidió. –

+0

Esto resuelve el propósito, pero ¿cómo preservar los espacios en blanco? Parece que el script awk los reemplaza con solo un espacio en blanco. – Guru

6

Haga una llamada al sed que escribe el script sed y otro para usarlo? Si sus listas están en archivos listA y listB, entonces:

paste -d : listA listB | sed 's/\([^:]*\):\([^:]*\)/s%\1%\2%/' > sed.script 
sed -f sed.script files.to.be.mapped.* 

Estoy haciendo algunas suposiciones generalizadas acerca de 'palabras' no contiene o bien dos puntos o símbolos por ciento, pero se puede adaptar alrededor de eso. Algunas versiones de sed tienen límites superiores en la cantidad de comandos que se pueden especificar; si eso es un problema porque sus listas de palabras son lo suficientemente grandes, entonces puede que tenga que dividir el script sed generado en archivos separados que se aplican, o cambiar para usar algo sin el límite (Perl, por ejemplo).

Otro elemento a tener en cuenta es la secuencia de cambios. Si desea intercambiar dos palabras, debe elaborar sus listas de palabras cuidadosamente. En general, si asigna (1) palabraA a palabraB y (2) palabraB a palabraC, importa si el script sed hace mapeo (1) antes o después del mapeo (2).

La secuencia de comandos que se muestra no tiene cuidado con los límites de las palabras; puede tener cuidado con ellos de varias maneras, dependiendo de la versión de sed que esté utilizando y sus criterios para lo que constituye una palabra.

+0

También existe el problema potencial de que una palabra en B esté total o parcialmente en A. Una solución verdadera probablemente requeriría dividir la entrada en palabras y cambiarlas una vez o nunca. – lhf

+0

esto produce el error: $ paste -d: listA listB | sed 's/\ ([^:] * \): \ ([^:] * \)/s% \ 1% \ 2%'> sed.script sed: -e expresión # 1, char 30: comando 's 'sin terminación – user248237dfsf

+0

@user, solucionó eso. –

1

Esto es bastante sencillo con Tcl:

set fA [open listA r] 
set fB [open listB r] 
set fin [open input.file r] 
set fout [open output.file w] 

# read listA and listB and create the mapping of corresponding lines 
while {[gets $fA strA] != -1} { 
    set strB [gets $fB] 
    lappend map $strA $strB 
} 

# apply the mapping to the input file 
puts $fout [string map $map [read $fin]] 

# if the file is large, do it line by line instead 
#while {[gets $fin line] != -1} { 
# puts $fout [string map $map $line] 
#} 

close $fA 
close $fB 
close $fin 
close $fout 

file rename output.file input.file 
+0

+1 para el uso de Tcl! –

1

se puede hacer esto en bash. Obtenga sus listas en matrices.

listA=(a b c) 
listB=(d e f) 
data=$(<file) 
echo "${data//${listA[2]}/${listB[2]}}" #change the 3rd element. Redirect to file where necessary 
-1

Uso tr (1) (traducir o eliminar caracteres):

cat file | tr 'abc' 'XYZ' > file_new 
mv file_new file 
+1

él quiere substituir cadenas enteras no caracteres individuales –

2

que tenía que hacer algo similar, y terminó generando comandos sed basado en un archivo de mapa:

$ cat file.map 
abc => 123 
def => 456 
ghi => 789 

$ cat stuff.txt 
abc jdy kdt 
kdb def gbk 
qng pbf ghi 
non non non 
try one abc 

$ sed `cat file.map | awk '{print "-e s/"$1"/"$3"/"}'`<<<"`cat stuff.txt`" 
123 jdy kdt 
kdb 456 gbk 
qng pbf 789 
non non non 
try one 123 

Asegúrate de que tu capa soporta tantos parámetros para sed como tengas en tu mapa.

+0

hermoso one-liner !! – once

+0

Versión 'sed' y' bash' pura: 'sed -f <(sed 's/=> //; s # #/#; s # $ #/#; s #^# s/# 'archivo.map) stuff.txt'. – agc

Cuestiones relacionadas