Combina dos archivos con awk

9

File1.txt

item1   carA
item2   carB
item3   carC
item4   platD
item5   carE

File2.txt

carA  platA
carB  platB
carC  platC
carE  platE

Salida deseada:

item1   platA
item2   platB
item3   platC
item4   platD
item5   platE

¿Cómo puedo hacerlo?

pawana
fuente

Respuestas:

11

La respuesta a continuación se basa en un Q&A similar en SO con algunas modificaciones relevantes:

$ awk 'FNR==NR {dict[$1]=$2; next} {$2=($2 in dict) ? dict[$2] : $2}1' file2.txt file1.txt 
item1 platA
item2 platB
item3 platC
item4 platD
item5 platE

La idea es crear un mapa hash con índice y usarlo como diccionario.

Para la segunda pregunta que hizo en su comentario ( qué se debe cambiar si la segunda columna de file1.txtserá la sexta columna ):

Si el archivo de entrada será como file1b.txt:

item1 A5 B C D carA
item2 A4 1 2 3 carB
item3 A3 2 3 4 carC
item4 A2 4 5 6 platD
item5 A1 7 8 9 carE

El siguiente comando lo hará:

$ awk 'FNR==NR {dict[$1]=$2; next} {$2=($6 in dict) ? dict[$6] : $6;$3="";$4="";$5="";$6=""}1' file2.txt file1b.txt 
item1 platA    
item2 platB    
item3 platC    
item4 platD    
item5 platE    
Yaron
fuente
1
@pawana: he actualizado mi respuesta para resolver también su segunda pregunta en el comentario. Si he respondido tu pregunta, acéptala .
Yaron
6

Sé que dijiste awk, pero hay un joincomando para este propósito ...

{
  join -o 1.1,2.2 -1 2 -2 1 <(sort -k 2 File1.txt) <(sort -k 1 File2.txt)     
  join -v 1 -o 1.1,1.2 -1 2 -2 1 <(sort -k 2 File1.txt) <(sort -k 1 File2.txt) 
} | sort -k 1

Sería suficiente con el primer joincomando si no fuera por esta línea:

item4   platD

El comando básicamente dice: unirse basado en la segunda columna del primer archivo ( -1 2), y la primera columna del segundo archivo ( -2 1), y generar la primera columna del primer archivo y la segunda columna del segundo archivo ( -o 1.1,2.2). Eso solo muestra las líneas que se emparejaron. El segundo comando de unión dice casi lo mismo, pero dice mostrar las líneas del primer archivo que no se pudieron emparejar ( -v 1), y mostrar la primera columna del primer archivo y la segunda columna del primer archivo ( -o 1.1,1.2). Luego clasificamos la salida de ambos combinados. sort -k 1significa ordenar en base a la primera columna, y sort -k 2significa ordenar en base a la segunda. Es importante ordenar los archivos según la columna de unión antes de pasarlos join.

Ahora, escribí la clasificación dos veces, porque no me gusta llenar mis directorios con archivos si puedo evitarlo. Sin embargo, como dijo David Foerster, dependiendo del tamaño de los archivos, es posible que desee ordenar los archivos y guardarlos primero para no tener que esperar para ordenarlos dos veces. Para dar una idea de los tamaños, aquí está el tiempo que toma ordenar 1 millón y 10 millones de líneas en mi computadora:

$ ruby -e '(1..1000000).each {|i| puts "item#{i}   plat#{i}"}' | shuf > 1million.txt 
$ ruby -e '(1..10000000).each {|i| puts "item#{i}   plat#{i}"}' | shuf > 10million.txt 
$ head 10million.txt 
item530284   plat530284
item7946579   plat7946579
item1521735   plat1521735
item9762844   plat9762844
item2289811   plat2289811
item6878181   plat6878181
item7957075   plat7957075
item2527811   plat2527811
item5940907   plat5940907
item3289494   plat3289494
$ TIMEFORMAT=%E
$ time sort 1million.txt >/dev/null
1.547
$ time sort 10million.txt >/dev/null
19.187

Eso es 1.5 segundos para 1 millón de líneas y 19 segundos para 10 millones de líneas.

JoL
fuente
En este caso, sería mejor almacenar los datos de entrada ordenados en archivos intermedios (temporales) porque la clasificación lleva bastante tiempo para conjuntos de datos de tamaño no trivial. De lo contrario +1.
David Foerster
@David Es un buen punto. Personalmente, realmente no me gusta tener que crear archivos intermedios, pero también estoy impaciente con los procesos de larga ejecución. Me preguntaba qué "tamaño trivial" sería, así que hice un pequeño punto de referencia y lo agregué a la respuesta junto con su sugerencia.
JoL
Ordenar 1 millones de registros es lo suficientemente rápido en computadoras de escritorio razonablemente modernas. Con 2 más 3 órdenes de magnitud, más cosas comienzan a volverse interesantes. En cualquier caso, el tiempo transcurrido (real) ( %Een el formato de tiempo) es menos interesante para medir el rendimiento computacional. El tiempo de CPU del modo de usuario ( %Uo simplemente una TIMEFORMATvariable no establecida) sería mucho más significativo.
David Foerster
@David No estoy realmente familiarizado con los casos de uso para los diferentes momentos. ¿Por qué es más interesante? El tiempo transcurrido es lo que coincide con el tiempo que realmente estoy esperando. Para el comando de 1.5 segundos, obtengo 4.5 segundos con %U.
JoL
1
El tiempo transcurrido se ve afectado por el tiempo dedicado a esperar otras tareas que se ejecutan en el mismo sistema y al bloqueo de las solicitudes de E / S. (Usuario) El tiempo de CPU no es. Por lo general, cuando se compara la velocidad de los algoritmos vinculados computacionalmente, uno quiere ignorar las E / S y evitar errores de medición debido a otras tareas en segundo plano. La pregunta importante es "¿Cuánto cálculo requiere este algoritmo en ese conjunto de datos?" en lugar de "¿Cuánto tiempo pasó mi computadora en todas sus tareas mientras esperaba que se completara ese cálculo?"
David Foerster