Reemplazar datos en posiciones específicas en el file txt utilizando datos de otro file

Tengo un file de text en el siguiente formatting:

$data This is the experimental data good data This is good file datafile 1 4324 3673 6.2e+11 7687 67576 2 3565 8768 8760 5780 8778 "This is line '2'" 3 7656 8793 -3e+11 7099 79909 4 8768 8965 8769 9879 0970 5 5878 9879 7.970e-1 9070 0709799 . . . 100000 3655 6868 97879 96879 69899 $.endfile 

Quiero replace los datos de la tercera y cuarta columna de la fila '2' a '100000' con los datos de otros dos files de text que tienen una columna de 99999 filas cada uno.

¿Cómo puedo hacer esto usando awk , sed o cualquier otro command de Unix? Tenga en count que el delimitador de columna es espacio.

Los otros dos files de text tienen 99999 líneas cada uno, y ambos tienen el siguiente formatting:

 12414 12421 36347 3.4e+3 -3.5e22 987983 . . . 87698 

Como no ha solicitado una solución 100% awk , ofreceré un híbrido que (a) puede, posiblemente, ser más fácil de entender, y (b) no enfatiza los límites de memory de awk :

 awk ' $1 == 2 { secondpart = 1 } { if (!secondpart) { print > "top" } else { print $1, $2 > "left" print $5, $6, $7, $8, $9 > "right" } }' a (cat top; paste -d" " left bc right) > new_a rm top left right 

O podemos eliminar uno de los files temporales y acortar el script con un command:

 (awk ' $1 == 2 { secondpart = 1 } { if (!secondpart) { print } else { print $1, $2 > "left" print $5, $6, $7, $8, $9 > "right" } }' a; paste -d" " left bc right) > new_a rm left right 

Esto colocará algunos espacios adicionales al final de las líneas de la salida, y perderá datos del file a si alguna línea tiene más de nueve campos (columnas). Si se trata de problemas, se pueden solucionar con bastante facilidad.

Una manera extraña:

 awk '{if(FNR==NR){f2[FNR+1]=$1;} else{ if(FNR==1){k++;} if(k==1){f3[FNR+1]=$1} else{if($1~/^[0-9]+/ && $1>1){$3=f2[$1];$4=f3[$1];} print} }}' file2 file3 file1 

Esto es lo mismo escrito como un guión comentado para mayor claridad:

 #!/usr/local/bin/gawk -f { ## NR is the current line number, irrespective of ## which input file is being read. FNR is the line ## number of the current file. It is reset to 1 each ## time a new file is opened. Therefore, FNR will be ## equal to NR only while the 1st file is being read. if(FNR==NR){ ## If this is the 1st file, save its 1st field ## in the array f2. The key of the array is the ## line number of the current file plus one. This is ## because you want to start modifying from row '2' onwards. ## Therefore, presumbly, you want the 1st row of file2 to ## be the value for row '2' of your data file.. f2[FNR+1]=$1; } ## If this is not the 1st file else{ ## If this is the 1st line of the current file if(FNR==1){ ## Increase the value of the variable k by 1. k++; } ## If k is currently 1, this means that the above has only ## been run once so we are currently reading the 1nd file. if(k==1){ ## Save the 1st field of this file (file3 in your example) ## in the array f3. The key of the array is the ## line number of the current file plus one. f3[FNR+1]=$1 } ## If k is not 1, we are reading the 3rd file. In this case, ## your actual data. else{ ## If the 1st field is a number and is greater than 1. ## In other words, if this is one of the lines you want ## to change. if($1~/^[0-9]+/ && $1>1){ ## Set the 3rd field to be the value saved in the array ## f2 for the value of $1. $3=f2[$1]; ## Set the 4th field to be the value saved in the array ## f3 for the value of $1. $4=f3[$1]; } ## Print the current line. Since this is outside the ## previous if block, it will print all lines irrespective ## of whether they've been modified. print; } } } 

Una forma de Perl:

 perl -lane 'BEGIN{ open(A,"file2"); while(<A>){chomp; $f2{$.+1}=$_;} open(B,"file3"); while(<B>){chomp; $f3{$.+1}=$_;}} if($F[0]=~/^\d+$/ && $F[0]>1){$F[2]=$f2{$F[0]}; $F[3]=$f3{$F[0]}} print "@F"' file1 

Explicación

  • -lane : el l eliminará automáticamente las nuevas líneas al final de cada línea de input (lo mismo que chomp ) y agregará una nueva línea a cada statement de print . El a dividirá automáticamente cada línea de input en espacios en blanco en la matriz @F , haciendo que perl se ejecute como awk. La n significa "ejecutar el script provisto por -e en cada línea del file de input.
  • BEGIN{...} : esto se ejecuta antes de que se lea el file de input. En este caso, estoy abriendo cada uno de los files extra y guardando su contenido en los hash %f2 y %f3 . Esto es básicamente lo mismo que las matrices awk que utilicé arriba.
  • if($F[0]=~/^\d+$/ && $F[0]>1){...} : de nuevo, esta es la misma lógica que en el script awk. Reemplazará los campos con las inputs correspondientes de cada file.
  • print "@F" : esto imprimirá todos los campos.
 { { paste -d\ /dev/fd/[345] | sed 's/ \( [^ ]*\)\(.*\)/\2\1/' } 3<<FILE1 4<<FILE2 5<<FILE3 $(<file1 sed '1,/^1/w /dev/fd/2 /^2/,$!d;s/ [^ ]*//4;s// /3') FILE1 $(<file2 tr -s \\n) FILE2 $(<file3 tr -s \\n) FILE3 } 2>&1 

En la secuencia de command anterior, hago una buena cantidad de malabares de input / salida. Simplemente se hace. file[23] son en realidad idénticos; ambos son una copy de tus 99.999 líneas de filas 3/4. Eso deja el file1 – es esencialmente el file exacto en su ejemplo anterior, pero la línea 5 está duplicada en 6 y 7 para que coincida con el file[23] .

Básicamente, cada file solo tiene su propio descriptor de file y su propio fragment de preparación. file[23] casi no tiene preparación, simplemente aprieta todos los caracteres repetidos \n ewline en uno, por lo que las líneas en blanco desaparecen.

file1 obtiene un poco más Primero, todas las líneas hasta e incluyendo la primera línea que comienza con 1 se escriben en stderr . Luego se borran de la salida, por lo que solo salen a >&2 . Next sed selecciona cols 3/4 y las reemplaza con un solo espacio, lo que significa que donde estaban ahora hay dos espacios espaciales consecutivos.

paste reúne todos los descriptores de file y los pega todos juntos, separados por espacios. Luego, sed intenta intercambiar la primera secuencia de caracteres no espaciales inmediatamente después de dos caracteres espaciales con todo a partir de entonces.

Por último, los descriptores de files para stderr y stdout se unen en stdout . El resultado es así:

SALIDA

 $data This is the experimental data good data This is good file datafile 1 4324 3673 6.2e+11 7687 67576 2 3565 8768 12414 12414 8778 3 7656 8793 12421 12421 79909 4 8768 8965 36347 36347 0970 5 5878 9879 3.4e+3 3.4e+3 0709799 6 5878 9879 -3.5e22 -3.5e22 0709799 7 5878 9879 987983 987983 0709799 . . . . . . . . . 100000 3655 6868 87698 87698 69899 $.endfile 

aquí es lo que surgió.

sus datos están en a.txt, la tercera columna está en b.txt (puse el nombre del día de la semana para mayor claridad, esto también funcionará con el número).

 mybox $ cat b.txt day monday tuesday wednesday thursday friday saturday mybox $ cat a.txt 1 4324 3673 6.2e+11 7687 67576 2 3565 8768 8760 5780 8778 3 7656 8793 -3e+11 7099 79909 4 8768 8965 8769 9879 0970 5 5878 9879 7.970e-1 9070 0709799 100000 3655 6868 97879 96879 69899 mybox $ cat ul.awk FILENAME == "b.txt" { for (i=2;i<=NF;i++) value_one[i-1]=$i ; next ; } {printf "%s %s %s %s %s %s\n",$1,$2,value_one[FNR],$4,$5,$6} mybox $ awk -f ul.awk b.txt a.txt 1 4324 monday 6.2e+11 7687 67576 2 3565 tuesday 8760 5780 8778 3 7656 wednesday -3e+11 7099 79909 4 8768 thursday 8769 9879 0970 5 5878 friday 7.970e-1 9070 0709799 100000 3655 saturday 97879 96879 69899 

sin embargo, no estoy seguro de que awk pueda manejar 99999 columnas .

¿Es esto lo que estás buscando? (aparte de fusionar solo un file)

edite 1 columna simple de b.txt (por cierto, esto levanta cualquier problema en awk).

 mybox $ cat a.txt 1 4324 3673 6.2e+11 7687 67576 2 3565 8768 8760 5780 8778 3 7656 8793 -3e+11 7099 79909 4 8768 8965 8769 9879 0970 5 5878 9879 7.970e-1 9070 0709799 100000 3655 6868 97879 96879 69899 mybox $ cat b.txt monday tuesday wednesday thursday friday saturday 

contenido de ul.awk

 FILENAME == "b.txt" { value[FNR]=$i ; } FILENAME != "b.txt" { printf "%s %s %s %s %s %s\n",$1,$2,value[FNR],$4,$5,$6} mybox $ awk -f ul.awk b.txt a.txt 1 4324 monday 6.2e+11 7687 67576 2 3565 tuesday 8760 5780 8778 3 7656 wednesday -3e+11 7099 79909 4 8768 thursday 8769 9879 0970 5 5878 friday 7.970e-1 9070 0709799 100000 3655 saturday 97879 96879 69899 

¿Esto se está acercando?

Otro modo extraño sin arreglos, es un poco desorderado así que lo intentaré y lo limpiaré más tarde

 awk 'function get(file,L) {x=1 while ( (getline < file) > 0) {if(NR==x)y=$0;x++} close(file) return y } ARGV[1]==FILENAME{d=$0;a=get(ARGV[2],$0);b=get(ARGV[3],$0);$0=d;$2=a;$3=b;print }' file file1 file2