Tengo un archivo enorme separado por tabulaciones formateado como este
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11
Me gustaría transponerlo de una manera eficiente usando solo comandos bash (podría escribir una secuencia de comandos Perl de diez líneas aproximadamente para hacer eso, pero debería ser más lento de ejecutar que las funciones nativas de bash). Entonces la salida debería verse como
X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11
Pensé en una solución como esta
cols=`head -n 1 input | wc -w`
for (( i=1; i <= $cols; i++))
do cut -f $i input | tr $'\n' $'\t' | sed -e "s/\t$/\n/g" >> output
done
Pero es lento y no parece la solución más eficiente. He visto una solución para vi en esta publicación , pero sigue siendo demasiado lenta. ¿Alguna idea / sugerencia / idea brillante? :-)
Respuestas:
salida
Rendimiento frente a la solución Perl de Jonathan en un archivo de 10000 líneas
EDITAR por Ed Morton (@ ghostdog74 no dude en eliminarlo si no lo aprueba).
Quizás esta versión con algunos nombres de variables más explícitos ayude a responder algunas de las preguntas a continuación y, en general, aclare lo que está haciendo el script. También usa pestañas como separador que el OP había solicitado originalmente para manejar campos vacíos y, casualmente, embellece un poco la salida para este caso particular.
Las soluciones anteriores funcionarán en cualquier awk (excepto el awk antiguo y roto, por supuesto, hay YMMV).
Sin embargo, las soluciones anteriores leen todo el archivo en la memoria; si los archivos de entrada son demasiado grandes para eso, puede hacer esto:
que casi no usa memoria pero lee el archivo de entrada una vez por número de campos en una línea, por lo que será mucho más lento que la versión que lee el archivo completo en la memoria. También asume que el número de campos es el mismo en cada línea y usa GNU awk para
ENDFILE
y,ARGIND
pero cualquier awk puede hacer lo mismo con pruebas enFNR==1
yEND
.fuente
Otra opción es utilizar
rs
:-c
cambia el separador de la columna de entrada,-C
cambia el separador de la columna de salida y-T
transpone filas y columnas. No lo utilice en-t
lugar de-T
, porque utiliza un número de filas y columnas calculado automáticamente que no suele ser correcto.rs
, que lleva el nombre de la función de remodelación en APL, viene con BSD y OS X, pero debería estar disponible en administradores de paquetes en otras plataformas.Una segunda opción es usar Ruby:
Una tercera opción es utilizar
jq
:jq -R .
imprime cada línea de entrada como un literal de cadena JSON,-s
(--slurp
) crea una matriz para las líneas de entrada después de analizar cada línea como JSON y-r
(--raw-output
) genera el contenido de cadenas en lugar de literales de cadena JSON. El/
operador está sobrecargado para dividir cadenas.fuente
rs
, ¡gracias por el puntero! (El enlace es para Debian; el upstream parece ser mirbsd.org/MirOS/dist/mir/rs )rs
eso viene con OS X,-c
solo establece el separador de columna de entrada en una pestaña.$'\t'
TTC TTA TTC TTC TTT
, la ejecuciónrs -c' ' -C' ' -T < rows.seq > cols.seq
dars: no memory: Cannot allocate memory
. Este es un sistema que ejecuta FreeBSD 11.0-RELEASE con 32 GB de RAM. Entonces, supongo quers
pone todo en RAM, lo que es bueno para la velocidad, pero no para datos grandes.Una solución de Python:
Lo anterior se basa en lo siguiente:
Este código asume que cada línea tiene el mismo número de columnas (no se realiza ningún relleno).
fuente
l.split()
porl.strip().split()
(Python 2.7), de lo contrario, la última línea de la salida está dañada. Funciona para separadores de columnas arbitrarios, usel.strip().split(sep)
ysep.join(c)
si su separador está almacenado en variablesep
.el proyecto de transposición en sourceforge es un programa en C similar a coreutil para exactamente eso.
fuente
-b
y-f
.Pure BASH, sin proceso adicional. Un buen ejercicio:
fuente
printf "%s\t" "${array[$COUNTER]}"
Eche un vistazo a GNU datamash que se puede usar como
datamash transpose
. Una versión futura también admitirá la tabulación cruzada (tablas dinámicas)fuente
Aquí hay un script de Perl moderadamente sólido para hacer el trabajo. Hay muchas analogías estructurales con la
awk
solución de @ ghostdog74 .Con el tamaño de los datos de la muestra, la diferencia de rendimiento entre perl y awk fue insignificante (1 milisegundo de un total de 7). Con un conjunto de datos más grande (matriz de 100x100, entradas de 6 a 8 caracteres cada una), perl superó ligeramente a awk: 0,026 s frente a 0,042 s. Es probable que ninguno de los dos sea un problema.
Tiempos representativos para Perl 5.10.1 (32 bits) vs awk (versión 20040207 cuando se le da '-V') vs gawk 3.1.7 (32 bits) en MacOS X 10.5.8 en un archivo que contiene 10,000 líneas con 5 columnas por línea:
Tenga en cuenta que gawk es mucho más rápido que awk en esta máquina, pero aún más lento que perl. Claramente, su millaje variará.
fuente
Si lo ha
sc
instalado, puede hacer:fuente
sc
nombra sus columnas como uno o una combinación de dos caracteres. El límite es26 + 26^2 = 702
.Hay una utilidad especialmente diseñada para esto,
Utilidad GNU datamash
Tomado de este sitio, https://www.gnu.org/software/datamash/ y http://www.thelinuxrain.com/articles/transposing-rows-and-columns-3-methods
fuente
Suponiendo que todas sus filas tienen el mismo número de campos, este programa awk resuelve el problema:
En palabras, a medida que recorre las filas, para cada campo
f
crece un ':' - cadena separada quecol[f]
contiene los elementos de ese campo. Una vez que haya terminado con todas las filas, imprima cada una de esas cadenas en una línea separada. Luego puede sustituir ':' por el separador que desea (digamos, un espacio) canalizando la salidatr ':' ' '
.Ejemplo:
fuente
GNU datamash se adapta perfectamente a este problema con solo una línea de código y potencialmente arbitrariamente grande.
fuente
Una solución hackish perl puede ser así. Es bueno porque no carga todo el archivo en la memoria, imprime archivos temporales intermedios y luego usa la maravillosa pasta
fuente
La única mejora que puedo ver en su propio ejemplo es el uso de awk que reducirá la cantidad de procesos que se ejecutan y la cantidad de datos que se canalizan entre ellos:
fuente
Normalmente uso este pequeño
awk
fragmento para este requisito:Esto simplemente carga todos los datos en una matriz bidimensional
a[line,column]
y luego los vuelve a imprimir comoa[column,line]
, de modo que transpone la entrada dada.Esto necesita realizar un seguimiento de la
max
cantidad inmensa de columnas que tiene el archivo inicial, de modo que se use como el número de filas para imprimir.fuente
Usé la solución de fgm (¡gracias fgm!), Pero necesitaba eliminar los caracteres de tabulación al final de cada fila, así que modifiqué el script así:
fuente
Solo estaba buscando una transposición de bash similar pero con soporte para relleno. Aquí está el script que escribí basado en la solución de fgm, que parece funcionar. Si puede ser de ayuda ...
fuente
Estaba buscando una solución para transponer cualquier tipo de matriz (nxn o mxn) con cualquier tipo de datos (números o datos) y obtuve la siguiente solución:
fuente
Si solo desea tomar una sola línea (delimitada por comas) $ N de un archivo y convertirla en una columna:
fuente
No es muy elegante, pero este comando de "línea única" resuelve el problema rápidamente:
Aquí cols es el número de columnas, donde puede reemplazar 4 por
head -n 1 input | wc -w
.fuente
Otra
awk
solución y entrada limitada con el tamaño de memoria que tienes.Esto une cada posición del mismo número archivado en juntos e
END
imprime el resultado que sería la primera fila en la primera columna, la segunda fila en la segunda columna, etc.fuente
Algunos ejemplos de utilidades estándar * nix , no se necesitan archivos temporales. NB: el OP quería una solución eficiente (es decir, más rápida), y las respuestas principales suelen ser más rápidas que esta respuesta. Estas frases breves son para quienes gustan de las herramientas de software * nix , por cualquier motivo. En casos raros ( por ejemplo, IO y memoria escasos), estos fragmentos pueden ser más rápidos que algunas de las respuestas principales.
Llame al archivo de entrada foo .
Si sabemos que foo tiene cuatro columnas:
Si no sabemos cuántas columnas tiene foo :
xargs
tiene un límite de tamaño y, por lo tanto, haría un trabajo incompleto con un archivo largo. ¿Qué límite de tamaño depende del sistema, por ejemplo:tr
&echo
:... o si se desconoce el número de columnas:
El uso
set
, que comoxargs
, tiene limitaciones similares basadas en el tamaño de la línea de comandos:fuente
awk
.cut
,head
,echo
, Etc no son más POSIX código shell compatible que unawk
guión es - todos ellos son estándar en todas las instalaciones de UNIX. Simplemente no hay razón para usar un conjunto de herramientas que en combinación requieran que tenga cuidado con el contenido de su archivo de entrada y el directorio desde el que ejecuta el script cuando puede usar awk y el resultado final es más rápido y más sólido .for f in cut head xargs seq awk ; do wc -c $(which $f) ; done
cuando el almacenamiento es demasiado lento o el IO es demasiado bajo, los intérpretes más grandes empeoran las cosas sin importar lo buenos que serían en circunstancias más ideales. Razón # 2: awk , (o la mayoría de los idiomas), también sufre de una curva de aprendizaje más pronunciada que una pequeña utilidad diseñada para hacer bien una cosa. Cuando el tiempo de ejecución es más barato que las horas de trabajo del codificador, la codificación sencilla con "herramientas de software" ahorra dinero.otra versión con
set
eval
fuente
Otra variante de bash
Guión
Salida
fuente
Aquí tienes una solución de Haskell. Cuando se compila con -O2, se ejecuta un poco más rápido que el awk de ghostdog y un poco más lento que el
cpython de Stephan en mi máquina para las líneas de entrada repetidas de "Hola mundo". Lamentablemente, el soporte de GHC para pasar código de línea de comando no existe hasta donde yo sé, por lo que tendrá que escribirlo en un archivo usted mismo. Truncará las filas a la longitud de la fila más corta.fuente
Una solución awk que almacena toda la matriz en la memoria.
Pero podemos "recorrer" el archivo tantas veces como las filas de salida sean necesarias:
Cuál (para un recuento bajo de filas de salida es más rápido que el código anterior).
fuente
Aquí hay un resumen de Bash que se basa simplemente en convertir cada línea en una columna y
paste
juntarlas:m.txt:
crea el
tmp1
archivo para que no esté vacío.lee cada línea y la transforma en una columna usando
tr
pega la nueva columna al
tmp1
archivolas copias resultan de nuevo en
tmp1
.PD: Tenía muchas ganas de usar descriptores io pero no pude hacer que funcionaran.
fuente
Un delineador con R ...
fuente
He usado a continuación dos scripts para realizar operaciones similares antes. El primero está en awk, que es mucho más rápido que el segundo, que está en bash "puro". Es posible que pueda adaptarlo a su propia aplicación.
fuente