ordenar pero mantener la línea de encabezado en la parte superior

56

Estoy obteniendo resultados de un programa que primero produce una línea que es un montón de encabezados de columna, y luego un montón de líneas de datos. Quiero cortar varias columnas de este resultado y verlo ordenado de acuerdo con varias columnas. Sin las cabeceras, el corte y la clasificación se lleva a cabo fácilmente a través de la -kopción de sort, junto con cuto awkpara ver un subconjunto de las columnas. Sin embargo, este método de clasificación mezcla los encabezados de columna con el resto de las líneas de salida. ¿Hay una manera fácil de mantener los encabezados en la parte superior?

jonderry
fuente
1
Encontré el siguiente enlace . Sin embargo, no puedo hacer que esta técnica { head -1; sort; }funcione. Siempre elimina un montón de texto después de la primera línea. ¿Alguien sabe por qué sucede esto?
jonderry
1
Sospecho que es porque headestá leyendo más de una línea en un búfer y tirando la mayor parte. Mi sedidea tuvo el mismo problema.
Andy
@jonderry: esa técnica solo funciona con lseekentrada capaz, por lo que no funcionará cuando se lea desde una tubería. Funcionará si redirige a un archivo >outfiley luego se ejecuta{ head -n 1; sort; } <outfile
don_crissti

Respuestas:

58

Robar la idea de Andy y convertirla en una función para que sea más fácil de usar:

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}

Ahora puedo hacer:

$ ps -o pid,comm | body sort -k2
  PID COMMAND
24759 bash
31276 bash
31032 less
31177 less
31020 man
31167 man
...

$ ps -o pid,comm | body grep less
  PID COMMAND
31032 less
31177 less
Mikel
fuente
ps -C COMMANDpuede ser más apropiado que grep COMMAND, pero es solo un ejemplo. Además, no puede usar -Csi también usó otra opción de selección como -U.
Mikel
O tal vez debería llamarse body? Como en body sorto body grep. Pensamientos?
Mikel
3
Renombrado de headera body, porque estás haciendo la acción en el cuerpo. Ojalá eso tenga más sentido.
Mikel
2
Recuerde llamar bodya todos los participantes posteriores de la tubería:ps -o pid,comm | body grep less | body sort -k1nr
obispo
1
@Tim Puedes simplemente escribir <foo body sort -k2o body sort -k2 <foo. Solo un personaje extra de lo que querías.
Mikel
37

Puede mantener el encabezado en la parte superior de esta manera con bash:

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

O hazlo con perl:

command | perl -e 'print scalar (<>); print sort { ... } <>'
Andy
fuente
2
+1 impresionante Vale la pena agruparlo como una función de shell, creo.
Mikel
1
+1, ¿alguna razón por la cual es preferible una subshell, o está {}bien en lugar de ()?
jonderry
2
IFS=desactiva la división de palabras al leer la entrada. No creo que sea necesario al leer $REPLY. echoexpandirá la barra invertida si xpg_echose establece (no el valor predeterminado); printfEs más seguro en ese caso. echo $REPLYsin comillas condensará espacios en blanco; Creo que echo "$REPLY"debería estar bien. read -res necesario si la entrada puede contener escapes de barra invertida. Algo de esto podría depender de la versión bash.
Andy
1
@Andy: Wow, tienes razón, diferentes reglas para read REPLY; echo $REPLY(tiras de espacios iniciales ) y read; echo $REPLY(no).
Mikel
1
@Andy: IIRC, el valor predeterminado de xpg_echodepende de su sistema, por ejemplo, en Solaris, creo que su valor predeterminado es verdadero. Es por eso que a Gilles le gusta printftanto: es lo único con un comportamiento predecible.
Mikel el
23

Encontré una buena versión awk que funciona muy bien en los scripts:

awk 'NR == 1; NR > 1 {print $0 | "sort -n"}'
Michael Kuhn
fuente
1
Me gusta esto, pero requiere un poco de explicación: la tubería está dentro del script awk. ¿Cómo funciona? ¿Está llamando al sortcomando externamente? ¿Alguien sabe de al menos un enlace a una página que explique el uso de tuberías dentro de awk?
Comodín el
@Wildcard puede consultar la página del manual oficial o este manual .
lapo
4

Hackish pero efectivo: anteponer 0todas las líneas de encabezado y 1todas las demás líneas antes de ordenar. Despoja al primer personaje después de ordenar.

… |
awk '{print (NR <= 2 ? "0 " : "1 ") $0}' |
sort -k 1 -k… |
cut -b 3-
Gilles 'SO- deja de ser malvado'
fuente
3

Aquí hay un poco de ruido de línea perl mágico a través del cual puede canalizar su salida para ordenar todo, pero mantenga la primera línea en la parte superior: perl -e 'print scalar <>, sort <>;'

Ryan Thompson
fuente
2

Probé la command | {head -1; sort; }solución y puedo confirmar que realmente arruina las cosas : headlee en varias líneas desde la tubería, luego emite solo la primera. Entonces, el resto de la salida, que head no se leyó, se pasa a - ¡ sortNO el resto de la salida a partir de la línea 2!

El resultado es que le faltan líneas (¡y una línea parcial!) Que estaban al comienzo de la salida de su comando (excepto que todavía tiene la primera línea), un hecho que es fácil de confirmar agregando una tubería al wcfinal de la tubería anterior, ¡pero eso es extraordinariamente difícil de rastrear si no lo sabe! Pasé al menos 20 minutos tratando de averiguar por qué tenía una línea parcial (los primeros 100 bytes más o menos cortados) en mi salida antes de resolverla.

Lo que terminé haciendo, que funcionó muy bien y no requirió ejecutar el comando dos veces, fue:

myfile=$(mktemp)
whatever command you want to run > $myfile

head -1 $myfile
sed 1d $myfile | sort

rm $myfile

Si necesita poner la salida en un archivo, puede modificar esto para:

myfile=$(mktemp)
whatever command you want to run > $myfile

head -1 $myfile > outputfile
sed 1d $myfile | sort >> outputfile

rm $myfile
Comodín
fuente
Puede usar la función headincorporada de ksh93 o la lineutilidad (en sistemas que todavía tienen uno) o gnu-sed -u qo IFS=read -r line; printf '%s\n' "$line", que leen la entrada de un byte a la vez para evitar eso.
Stéphane Chazelas
1

Creo que esto es lo más fácil.

ps -ef | ( head -n 1 ; sort )

o esto que posiblemente sea más rápido ya que no crea un sub shell

ps -ef | { head -n 1 ; sort ; }

Otros usos geniales

barajar líneas después de la fila del encabezado

cat file.txt |  ( head -n 1 ; shuf )

líneas inversas después de la fila del encabezado

cat file.txt |  ( head -n 1 ; tac )
usuario2449151
fuente
2
Ver unix.stackexchange.com/questions/11856/… . Esto no es realmente una buena solución.
Comodín el
1
No funciona, cat file | { head -n 1 ; sort ; } > file2solo muestra la cabeza
Peter Krauss
0
command | head -1; command | tail -n +2 | sort
Sarva
fuente
44
Esto comienza commanddos veces. Por lo tanto, se limita a algunos comandos específicos. Sin embargo, para el pscomando solicitado en el ejemplo, funcionaría.
jofel
0

¡Simple y directo!

<command> | head -n 1; <command> | sed 1d | sort <....>
  • sed nd ---> 'n' especifica el número de línea, y 'd' significa eliminar.
Jatsui
fuente
1
Justo como Jofel comentó hace un año y medio sobre la respuesta de Sarva, esto comienza commanddos veces. Por lo tanto, no es realmente adecuado para su uso en una tubería.
Comodín el
0

Vine aquí buscando una solución para el comando w. Este comando muestra detalles de quién está conectado y qué está haciendo.

Para mostrar los resultados ordenados, pero con los encabezados en la parte superior (hay 2 líneas de encabezados), me decidí por:

w | head -n 2; w | tail -n +3 | sort

Obviamente, esto ejecuta el comando wdos veces y, por lo tanto, puede no ser adecuado para todas las situaciones. Sin embargo, para su ventaja es sustancialmente más fácil de recordar.

Tenga en cuenta que los tail -n +3medios 'muestran todas las líneas desde el 3 en adelante' (ver man tailpara más detalles).

Robert
fuente
-2

Trata de hacerlo:

wc -l file_name | tail -n $(awk '{print $1-1}') file_name | sort
Barry
fuente
3
No lo entiendo
Pierre.Vriens