Reorganizar columnas usando corte

135

Tengo un archivo en el siguiente formato

Columna1 Columna2
str1 1
str2 2
str3 3

Quiero que las columnas se reorganicen. Intenté debajo del comando

cortar -f2,1 archivo.txt

El comando no reordena las columnas. ¿Alguna idea de por qué no funciona?

Gracias.

Booleano
fuente

Respuestas:

148

Para la cut(1)página del manual:

Use uno, y solo uno de -b, -c o -f. Cada LISTA está compuesta por un rango, o muchos rangos separados por comas. La entrada seleccionada se escribe en el mismo orden en que se lee y se escribe exactamente una vez.

Primero llega al campo 1, de modo que se imprime, seguido del campo 2.

Use en su awklugar:

awk '{ print $2 " " $1}' file.txt
Ignacio Vazquez-Abrams
fuente
12
Es una pena cutque no admita este comando intuitivo de reordenamiento. De todos modos, otro consejo: se puede utilizar awk's -FSy -OFSopciones para el uso de entrada personalizada y separadores de campo de salida (como -dy --output-delimiterpara cut).
malana
12
Lo sentimos, FSes una opción, OFSes una variable. egawk -v OFS=";" -F"\t" '{print $2,$1}'
malana
2
Nota para los usuarios de Windows de Git Bash: si tiene una salida extraña del comando anterior, que parece columnas que se anulan entre sí, el culpable es el retorno del carro. Cambie EOL en su archivo de CRLF a LF.
jakub.g
1
Alternativamente, si no desea cambiar el archivo de entrada, puede canalizarlo | sed 's/\r//' | antes de awk
conectarlo
2
Este es muy simple pero podría ser útil para algunos, simplemente reemplace el espacio con \ t para reordenar por pestañas, y en caso de que desee más columnas, puede hacerlo como por ejemploawk '{print $4 "\t" $2 "\t" $6 "\t" $7}' file
FatihSarigol
64

También puede combinar cuty paste:

paste <(cut -f2 file.txt) <(cut -f1 file.txt)

a través de comentarios: es posible evitar bashisms y eliminar una instancia de corte haciendo:

paste file.txt file.txt | cut -f2,3
Justin Kaeser
fuente
3
No estoy seguro si esto califica como "hábilmente", pero: f = file.txt pegar <(cortar -f2 $ f) <(cortar -f1 $ f). Además, noto que este método es el más fácil cuando tienes muchas columnas y quieres mover grandes bloques de ellas.
Michael Rusch
no funciona con celdas de longitudes variables en la misma columna
kraymer
2
@kraymer ¿Qué quieres decir? cutfunciona bien para columnas de longitud variable siempre que tenga un separador de columna único.
tripleee
1
Para eliminar el archivo redundante, probablemente podría usar tee:
JJW5432
2
Es posible evitar los bashismos y eliminar una instancia de cuthaciendo: paste file.txt file.txt | cut -f2,3
agc
7

usando solo el caparazón,

while read -r col1 col2
do
  echo $col2 $col1
done <"file"
ghostdog74
fuente
Esto es a menudo ineficiente. Por lo general, encontrará que el script Awk correspondiente es mucho más rápido, por ejemplo. También debe tener cuidado de citar los valores "$col2"y "$col1", podría haber metacaracteres de shell u otras travesuras en los datos.
tripleee
7

Puedes usar Perl para eso:

perl -ane 'print "$F[1] $F[0]\n"' < file.txt
  • -e opción significa ejecutar el comando después de que
  • -n significa leer línea por línea (abra el archivo, en este caso STDOUT, y repita las líneas)
  • -a significa dividir esas líneas en un vector llamado @F ("F" - como Field). Perl indexa vectores que comienzan desde 0 a diferencia del corte que indexa campos que comienzan desde el 1.
  • Puede agregar el patrón -F (sin espacio entre -F y el patrón ) para usar el patrón como un separador de campo al leer el archivo en lugar del espacio en blanco predeterminado

La ventaja de ejecutar perl es que (si conoce a Perl) puede hacer muchos más cálculos en F que reorganizar columnas.

Reunió
fuente
perlrun (1) afirma -a establece implícitamente -n pero si ejecuto sin -n set, parece que no se repite. impar.
Trenton
Que versión perl -ae printfunciona catpara mí
pwes
5

Utilizando join:

join -t $'\t' -o 1.2,1.1 file.txt file.txt

Notas:

  • -t $'\t'En GNU join el más intuitivo -t '\t' y sin la $falla, ( coreutils v8.28 y anteriores?); Probablemente sea un error que una solución alternativa $debería ser necesaria. Ver: unix join separator char .

  • joinnecesita dos nombres de archivo, aunque solo se esté trabajando en un archivo. Usar el mismo nombre dos veces engaña joinpara realizar la acción deseada.

  • Para sistemas con bajos recursos joinofrece una huella más pequeña que algunas de las herramientas utilizadas en otras respuestas:

    wc -c $(realpath `which cut join sed awk perl`) | head -n -1
      43224 /usr/bin/cut
      47320 /usr/bin/join
     109840 /bin/sed
     658072 /usr/bin/gawk
    2093624 /usr/bin/perl
agc
fuente
3

Acabo de trabajar en algo muy similar, no soy un experto, pero pensé que compartiría los comandos que he usado. Tenía un csv de varias columnas del que solo necesitaba 4 columnas y luego necesitaba reordenarlas.

Mi archivo era pipe '|' delimitado pero eso se puede cambiar.

LC_ALL=C cut -d$'|' -f1,2,3,8,10 ./file/location.txt | sed -E "s/(.*)\|(.*)\|(.*)\|(.*)\|(.*)/\3\|\5\|\1\|\2\|\4/" > ./newcsv.csv

¡Es cierto que es realmente duro y listo, pero se puede ajustar para adaptarse!

Chris Rymer
fuente
Esto no responde a la pregunta planteada. En el espíritu del desbordamiento de la pila, dedique tiempo a responder un problema antes de publicar.
Bill Gale
0

Usando sed

Use sed con subexpresiones anidadas de expresiones regulares básicas para capturar y reordenar el contenido de la columna. Este enfoque es más adecuado cuando hay un número limitado de cortes para reordenar columnas, como en este caso.

La idea básica es rodear porciones interesantes del patrón de búsqueda con \(y \), que se puede reproducir en el patrón de reemplazo con \#where #representa la posición secuencial de la subexpresión en el patrón de búsqueda.

Por ejemplo:

$ echo "foo bar" | sed "s/\(foo\) \(bar\)/\2 \1/"

rendimientos:

bar foo

El texto fuera de una subexpresión se escanea pero no se retiene para reproducirlo en la cadena de reemplazo.

Aunque la pregunta no discutió columnas de ancho fijo, discutiremos aquí ya que esta es una medida digna de cualquier solución planteada. Para simplificar, supongamos que el archivo está delimitado por espacios, aunque la solución se puede ampliar para otros delimitadores.

Espacios colapsados

Para ilustrar el uso más simple, supongamos que se pueden contraer múltiples espacios en espacios individuales, y los valores de la segunda columna se terminan con EOL (y no con espacio).

Expediente:

bash-3.2$ cat f
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  nl
0000040    s   t   r   2  sp  sp  sp  sp  sp  sp  sp   2  nl   s   t   r
0000060    3  sp  sp  sp  sp  sp  sp  sp   3  nl 
0000072

Transformar:

bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f
Column2 Column1
1 str1
2 str2
3 str3
bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  nl
0000020    1  sp   s   t   r   1  nl   2  sp   s   t   r   2  nl   3  sp
0000040    s   t   r   3  nl
0000045

Preservar anchos de columna

Extendamos ahora el método a un archivo con columnas de ancho constante, mientras permitimos que las columnas sean de diferentes anchos.

Expediente:

bash-3.2$ cat f2
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f2
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  sp
0000040   sp  sp  sp  sp  sp  nl   s   t   r   2  sp  sp  sp  sp  sp  sp
0000060   sp   2  sp  sp  sp  sp  sp  sp  nl   s   t   r   3  sp  sp  sp
0000100   sp  sp  sp  sp   3  sp  sp  sp  sp  sp  sp  nl
0000114

Transformar:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2
Column2 Column1
1       str1      
2       str2      
3       str3      
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   2  sp  sp  sp  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

Por último, aunque el ejemplo de la pregunta no tiene cadenas de longitud desigual, esta expresión sed respalda este caso.

Expediente:

bash-3.2$ cat f3
Column1    Column2
str1       1      
string2    2      
str3       3      

Transformar:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3
Column2 Column1   
1       str1      
2       string2   
3       str3    
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   i   n   g   2  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

Comparación con otros métodos de reordenamiento de columnas bajo shell

  • Sorprendentemente para una herramienta de manipulación de archivos, awk no es adecuado para cortar desde un campo hasta el final del registro. En sed esto se puede lograr utilizando expresiones regulares, por ejemplo, \(xxx.*$\)dónde xxxestá la expresión para que coincida con la columna.

  • Usar pegar y cortar subshells se vuelve complicado cuando se implementan scripts de shell. El código que funciona desde la línea de comandos no se analiza cuando se introduce dentro de un script de shell. Al menos esta fue mi experiencia (lo que me llevó a este enfoque).

Bill Gale
fuente
0

Ampliando la respuesta de @Met, también usando Perl:
Si la entrada y la salida están delimitadas por TAB:

perl -F'\t' -lane 'print join "\t", @F[1, 0]' in_file

Si la entrada y la salida están delimitadas por espacios en blanco:

perl -lane 'print join " ", @F[1, 0]' in_file

Aquí,
-ele dice a Perl que busque el código en línea, en lugar de en un archivo de script separado,
-nlee la línea de entrada 1 a la vez,
-lelimina el separador de registro de entrada ( \nen * NIX) después de leer la línea (similar a chomp) y agrega salida separador de registros ( \nen * NIX) para cada uno print,
-adivide la línea de entrada en el espacio en blanco en la matriz @F,
-F'\t'en combinación con -ala línea de entrada en las TAB, en lugar del espacio en blanco en la matriz @F.

@F[1, 0]es la matriz compuesta de los elementos segundo y primero de la matriz @F, en este orden. Recuerde que las matrices en Perl están indexadas a cero, mientras que los campos cutestán indexados a 1. Por lo tanto, los campos en @F[0, 1]son los mismos campos que en cut -f1,2.

Tenga en cuenta que dicha notación permite una manipulación más flexible de la entrada que en algunas otras respuestas publicadas anteriormente (que están bien para una tarea simple). Por ejemplo:

# reverses the order of fields:
perl -F'\t' -lane 'print join "\t", reverse @F' in_file

# prints last and first fields only:
perl -F'\t' -lane 'print join "\t", @F[-1, 0]' in_file
Timur Shtatland
fuente