¿Hay alguna forma de ignorar las líneas de encabezado en una ordenación UNIX?

102

Tengo un archivo de campo de ancho fijo que estoy tratando de ordenar usando la utilidad de clasificación UNIX (Cygwin, en mi caso).

El problema es que hay un encabezado de dos líneas en la parte superior del archivo que se ordena al final del archivo (ya que cada línea de encabezado comienza con dos puntos).

¿Hay alguna manera de decirle a sort "pasar las dos primeras líneas sin clasificar" o especificar un orden que clasifique las líneas de dos puntos en la parte superior? Las líneas restantes siempre comienzan con un número de 6 dígitos (que en realidad es la clave I estoy clasificando) si eso ayuda.

Ejemplo:

:0:12345
:1:6:2:3:8:4:2
010005TSTDOG_FOOD01
500123TSTMY_RADAR00
222334NOTALINEOUT01
477821USASHUTTLES21
325611LVEANOTHERS00

debería ordenar por:

:0:12345
:1:6:2:3:8:4:2
010005TSTDOG_FOOD01
222334NOTALINEOUT01
325611LVEANOTHERS00
477821USASHUTTLES21
500123TSTMY_RADAR00
Rob Gilliam
fuente
Para el registro: la línea de comando que estoy usando hasta ahora es "sort -t \\ -k1.1,1.6 <archivo>" [los datos pueden contener espacios, pero nunca contendrán una barra invertida]
Rob Gilliam

Respuestas:

125
(head -n 2 <file> && tail -n +3 <file> | sort) > newfile

Los paréntesis crean una subcapa, cerrando la salida estándar para que pueda canalizarla o redirigirla como si procediera de un solo comando.

BobS
fuente
Gracias; Acepto esta respuesta porque parece más completa y concisa (¡y entiendo lo que está haciendo!). Sin embargo, debería ser "head -n 2" :-)
Rob Gilliam
1
Gracias, arregló la parte de la 'cabeza'.
BobS
4
¿Hay alguna forma de que esta versión funcione con datos canalizados? Intenté con tee >(head -n $header_size) | tail -n +$header_size | sort, pero la cabeza parece correr detrás de la tail|sorttubería, por lo que el encabezado termina impreso al final. ¿Es esto determinista o una condición de carrera?
Damien Pollet
Probablemente podría armar algo que use catpara redirigir el stdin a un archivo temporal, luego ejecute el comando anterior en ese nuevo archivo, pero está comenzando a ponerse lo suficientemente feo como para que probablemente sea mejor usar una de las soluciones basadas en awk que se dan en las otras respuestas.
BobS
@DamienPollet: Vea la respuesta de Dave .
Jonathan Leffler
63

Si no le importa usar awk, puede aprovechar awklas capacidades de tubería integradas

p.ej.

extract_data | awk 'NR<3{print $0;next}{print $0| "sort -r"}' 

Esto imprime las dos primeras líneas textualmente y pasa el resto sort.

Tenga en cuenta que esto tiene la ventaja muy específica de poder clasificar selectivamente partes de una entrada canalizada. todos los otros métodos sugeridos solo ordenarán archivos planos que se pueden leer varias veces. Esto funciona en cualquier cosa.

Dave
fuente
2
Muy agradable y funciona con tuberías arbitrarias, ¡no solo con archivos!
lapo
4
Hermoso, awk nunca deja de sorprenderme. Además, no necesita el $0, printes suficiente.
nachocab
1
La respuesta de @SamWatkins freeseek es menos fea.
confesar.
¿Qué hace la opción -r para ordenar? ¿Se supone que esto es de tipo inverso?
gvrocha
32

Aquí hay una versión que funciona con datos canalizados:

(read -r; printf "%s\n" "$REPLY"; sort)

Si su encabezado tiene varias líneas:

(for i in $(seq $HEADER_ROWS); do read -r; printf "%s\n" "$REPLY"; done; sort)

Esta solución es de aquí

freeseek
fuente
9
bonito. para el caso de un solo encabezado que uso extract_data | (read h; echo "$h"; sort) , es lo suficientemente corto como para recordarlo. su ejemplo cubre más casos extremos. :) Esta es la mejor respuesta. trabaja en tuberias. no awk.
confesar.
1
Ok, he terminado esto y parece que bash hace todo lo posible para que esto funcione. En general, si codificó esto en C u otro idioma, no funcionaría porque stdio leería más que solo la primera línea de encabezado. Si lo ejecuta en un archivo que se puede buscar, bash lee un fragmento más grande (128 bytes en mi prueba), luego vuelve a buscar después del final de la primera línea. Si lo ejecuta en una tubería, bash lee un carácter a la vez hasta que pasa el final de la línea.
Sam Watkins
¡Agradable! Si solo te quieres comer el cabezazo, es aún más fácil de recordar:extract_data | (read; sort)
Jason Suárez
Este es casi perfecto, pero necesita usar "IFS = read" en lugar de "read" para mantener los espacios iniciales y finales.
Stanislav German-Evtushenko
6
Esta debería ser la respuesta aceptada en mi opinión. Simple, conciso y más flexible, ya que también funciona con datos canalizados.
Paul I
12

En casos simples, sedpuede hacer el trabajo con elegancia:

    your_script | (sed -u 1q; sort)

o equivalente,

    cat your_data | (sed -u 1q; sort)

La clave está en 1q: imprimir la primera línea (encabezado) y salir (dejando el resto de la entrada en sort).

Para el ejemplo dado, funcionará 2q.

El -uconmutador (sin búfer) es necesario para aquellos mensajes de correo electrónico sed(en particular, GNU) que de otro modo leerían la entrada en fragmentos, consumiendo así los datos por los que desea pasar sort.

Andrea
fuente
1
Hola, @Andrea; bienvenido a Stack Overflow. Me temo que su respuesta no funciona, al menos no cuando la estoy probando en Git Bash en Windows (pasé de Cygwin, el shell que estaba usando en un trabajo diferente hace 6 años). El comando sed extrae todos los datos del stdin, sin dejar ningún dato para pasar a ordenar. Intente cambiar el comando a cat your_data | (sed 1q; wc -l) para ver a qué me refiero.
Rob Gilliam
1
Esto podría funcionar si pasa la entrada por segunda vez al comando sed, así: cat sortMe.csv | (sed 1q sortMe.csv; sort -t, -k3 -rn)> sorted.csv
Harry Cramer
8

Puede usar tail -n +3 <file> | sort ...(tail generará el contenido del archivo desde la tercera línea).

Anton Kovalenko
fuente
4
head -2 <your_file> && nawk 'NR>2' <your_file> | sort

ejemplo:

> cat temp
10
8
1
2
3
4
5
> head -2 temp && nawk 'NR>2' temp | sort -r
10
8
5
4
3
2
1
Vijay
fuente
3

Solo se necesitan 2 líneas de código ...

head -1 test.txt > a.tmp; 
tail -n+2 test.txt | sort -n >> a.tmp;

Para datos numéricos, se requiere -n. Para la ordenación alfa, la -n no es necesaria.

Archivo de ejemplo:
$ cat test.txt

cabecera
8
5
100
1
-1

Resultado:
$ cat a.tmp

cabecera
-1
1
5
8
100

Ian Sherbin
fuente
1
¿No es esta básicamente la misma respuesta que la respuesta aceptada? (Excepto que el enfoque de BobS coloca el resultado en stdout, lo que le permite enviar el resultado a través de otros filtros antes de escribirlo en el archivo, si es necesario)
Rob Gilliam
1

Así que aquí hay una función bash donde los argumentos son exactamente como sort. Soporte de archivos y tuberías.

function skip_header_sort() {
    if [[ $# -gt 0 ]] && [[ -f ${@: -1} ]]; then
        local file=${@: -1}
        set -- "${@:1:$(($#-1))}"
    fi
    awk -vsargs="$*" 'NR<2{print; next}{print | "sort "sargs}' $file
}

Cómo funciona. Esta línea comprueba si hay al menos un argumento y si el último argumento es un archivo.

    if [[ $# -gt 0 ]] && [[ -f ${@: -1} ]]; then

Esto guarda el archivo en un argumento separado. Ya que estamos a punto de borrar el último argumento.

        local file=${@: -1}

Aquí eliminamos el último argumento. Dado que no queremos pasarlo como un argumento de clasificación.

        set -- "${@:1:$(($#-1))}"

Finalmente, hacemos la parte awk, pasando los argumentos (menos el último argumento si era el archivo) para ordenar awk. Esto fue sugerido originalmente por Dave y modificado para tomar argumentos de clasificación. Confiamos en el hecho de que $fileestará vacío si estamos canalizando, por lo tanto ignorado.

    awk -vsargs="$*" 'NR<2{print; next}{print | "sort "sargs}' $file

Ejemplo de uso con un archivo separado por comas.

$ cat /tmp/test
A,B,C
0,1,2
1,2,0
2,0,1

# SORT NUMERICALLY SECOND COLUMN
$ skip_header_sort -t, -nk2 /tmp/test
A,B,C
2,0,1
0,1,2
1,2,0

# SORT REVERSE NUMERICALLY THIRD COLUMN
$ cat /tmp/test | skip_header_sort -t, -nrk3
A,B,C
0,1,2
2,0,1
1,2,0
gripe
fuente
0

Con Python:

import sys
HEADER_ROWS=2

for _ in range(HEADER_ROWS):
    sys.stdout.write(next(sys.stdin))
for row in sorted(sys.stdin):
    sys.stdout.write(row)
cruzado
fuente
presupone que el sistema tiene Python instalado (el mío no)
Rob Gilliam
0

Aquí hay una función de shell bash derivada de las otras respuestas. Maneja tanto archivos como tuberías. El primer argumento es el nombre del archivo o '-' para stdin. Los argumentos restantes se pasan a ordenar. Un par de ejemplos:

$ hsort myfile.txt
$ head -n 100 myfile.txt | hsort -
$ hsort myfile.txt -k 2,2 | head -n 20 | hsort - -r

La función de shell:

hsort ()
{
   if [ "$1" == "-h" ]; then
       echo "Sort a file or standard input, treating the first line as a header.";
       echo "The first argument is the file or '-' for standard input. Additional";
       echo "arguments to sort follow the first argument, including other files.";
       echo "File syntax : $ hsort file [sort-options] [file...]";
       echo "STDIN syntax: $ hsort - [sort-options] [file...]";
       return 0;
   elif [ -f "$1" ]; then
       local file=$1;
       shift;
       (head -n 1 $file && tail -n +2 $file | sort $*);
   elif [ "$1" == "-" ]; then
       shift;
       (read -r; printf "%s\n" "$REPLY"; sort $*);
   else
       >&2 echo "Error. File not found: $1";
       >&2 echo "Use either 'hsort <file> [sort-options]' or 'hsort - [sort-options]'";
       return 1 ;
   fi
}
JonDeg
fuente
0

Esto es lo mismo que la respuesta de Ian Sherbin, pero mi implementación es: -

cut -d'|' -f3,4,7 $arg1 | uniq > filetmp.tc
head -1 filetmp.tc > file.tc;
tail -n+2 filetmp.tc | sort -t"|" -k2,2 >> file.tc;
Bik
fuente
-4
cat file_name.txt | sed 1d | sort 

Esto hará lo que quieras.

Sathish G
fuente
1) Esto solo elimina la línea del encabezado y ordena el resto, no ordena todo debajo de la línea del encabezado dejando el encabezado intacto. 2) elimina solo la primera línea, cuando el encabezado es en realidad dos líneas (lea la pregunta). 3) ¿Por qué utiliza "cat file_name.txt | sed 1d" cuando "sed 1d <file_name.txt" o incluso simplemente "sed 1d file_name.txt" tiene el mismo efecto?
Rob Gilliam