trier mais garder la ligne d'en-tête en haut

56

Je reçois des résultats d'un programme qui produit d'abord une ligne composée d'en-têtes de colonnes, puis de lignes de données. Je souhaite couper différentes colonnes de cette sortie et les afficher triées en fonction de différentes colonnes. Sans les en- têtes, la découpe et le tri est facilement accompli par la -kpossibilité d' sorten même temps que cutou awkpour afficher un sous - ensemble des colonnes. Cependant, cette méthode de tri mélange les en-têtes de colonne avec le reste des lignes de sortie. Existe-t-il un moyen simple de garder les en-têtes en haut?

Jonderry
la source
1
Je suis tombé sur le lien suivant . Cependant, je ne peux pas obtenir cette technique de { head -1; sort; }travail. Il supprime toujours une partie du texte après la première ligne. Est-ce que quelqu'un sait pourquoi cela se produit?
Jonderry
1
Je suppose que c’est parce qu’il headlit plus d’une ligne dans un tampon et qu’il en jette la majeure partie. Mon sedidée avait le même problème.
Andy
@jonderry - cette technique ne fonctionne qu'avec une lseekentrée capable, elle ne fonctionnera donc pas lors de la lecture d'un tuyau. Cela fonctionnera si vous redirigez vers un fichier >outfilepuis exécutez{ head -n 1; sort; } <outfile
don_crissti

Réponses:

58

Voler l'idée d'Andy et en faire une fonction pour en faciliter l'utilisation:

# 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"
    "$@"
}

Maintenant je peux faire:

$ 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
la source
ps -C COMMANDpeut-être plus approprié que grep COMMAND, mais ce n'est qu'un exemple. En outre, vous ne pouvez pas utiliser -Csi vous avez également utilisé une autre option de sélection telle que -U.
Mikel
Ou peut-être devrait-il s'appeler body? Comme dans body sortou body grep. Pensées?
Mikel
3
Renommé de headerà body, parce que vous faites l'action sur le corps. Espérons que cela a plus de sens.
Mikel
2
N'oubliez pas d'appeler bodytous les participants suivants au pipeline:ps -o pid,comm | body grep less | body sort -k1nr
évêque
1
@ Tim Vous pouvez simplement écrire <foo body sort -k2ou body sort -k2 <foo. Juste un caractère supplémentaire de ce que tu voulais.
Mikel
37

Vous pouvez garder l'en-tête en haut comme ceci avec bash:

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

Ou le faire avec perl:

command | perl -e 'print scalar (<>); print sort { ... } <>'
Andy
la source
2
+1 génial. Je pense que ça vaut la peine d’être groupé comme une fonction shell.
Mikel
1
+1, une raison quelconque pour laquelle un sous-shell est préférable, ou est {}ok au lieu de ()?
jeudi
2
IFS=désactive le fractionnement des mots lors de la lecture de l'entrée. Je ne pense pas que ce soit nécessaire pour lire $REPLY. echodéveloppera les échappements de barre oblique inversée si xpg_echoest défini (pas la valeur par défaut); printfest plus sûr dans ce cas. echo $REPLYsans guillemets va condenser les espaces; Je pense echo "$REPLY"que ça devrait aller. read -rest nécessaire si l'entrée peut contenir des échappements de barre oblique inversée. Une partie de ceci pourrait dépendre de la version de bash.
Andy
1
@Andy: Wow, vous avez raison, des règles différentes pour read REPLY; echo $REPLY( supprime les espaces de début ) et read; echo $REPLY(non).
Mikel
1
@Andy: IIRC, la valeur par défaut de xpg_echodépend de votre système. Par exemple, sous Solaris , la valeur par défaut est true. C'est pourquoi Gilles aime printftellement: c'est la seule chose avec un comportement prévisible.
Mikel
23

J'ai trouvé une belle version de awk qui fonctionne bien dans les scripts:

awk 'NR == 1; NR > 1 {print $0 | "sort -n"}'
Michael Kuhn
la source
1
J'aime ça, mais cela nécessite quelques explications - le tuyau est à l'intérieur du script awk. Comment ça marche? Est-ce qu'il appelle la sortcommande à l'extérieur? Est-ce que quelqu'un connaît au moins un lien vers une page expliquant l'utilisation de la pipe dans awk?
Wildcard
@Wildcard, vous pouvez consulter la page de manuel officielle ou cet apprêt .
Lapo
4

Hackish mais efficace: ajoutez avant 0toutes les lignes d'en-tête et 1toutes les autres lignes avant le tri. Dénudez le premier caractère après le tri.

… |
awk '{print (NR <= 2 ? "0 " : "1 ") $0}' |
sort -k 1 -k… |
cut -b 3-
Gilles, arrête de faire le mal
la source
3

Voici un bruit de ligne perl magique que vous pouvez diriger vers votre sortie pour tout trier, mais gardez la première ligne en haut: perl -e 'print scalar <>, sort <>;'

Ryan Thompson
la source
2

J'ai essayé la command | {head -1; sort; }solution et je peux confirmer que les choses vont vraiment mal - headlit plusieurs lignes à partir du tuyau, puis ne produit que la première. Ainsi, le reste de la sortie, qui head n'a pas été lu, est passé à sort--NOT le reste de la sortie à partir de la ligne 2!

Le résultat est qu'il vous manque des lignes (et une ligne partielle!) Qui figuraient au début de la sortie de votre commande (sauf que vous avez toujours la première ligne) - un fait facile à confirmer en ajoutant un tuyau wcà la fin de le pipeline ci-dessus - mais il est extrêmement difficile de retracer si vous ne le savez pas! J'ai passé au moins 20 minutes à essayer de comprendre pourquoi j'avais une ligne partielle (100 premiers octets ou plus coupés) dans ma sortie avant de la résoudre.

Ce que j'ai fini par faire, ce qui a fonctionné à merveille et n'a pas nécessité l'exécution de la commande deux fois, a été:

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

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

rm $myfile

Si vous devez mettre la sortie dans un fichier, vous pouvez le modifier pour:

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

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

rm $myfile
Wildcard
la source
Vous pouvez utiliser la fonction intégrée de headksh93 ou l' lineutilitaire (sur les systèmes qui en ont toujours un) ou gnu-sed -u qou IFS=read -r line; printf '%s\n' "$line", qui lit l'entrée un octet à la fois pour éviter cela.
Stéphane Chazelas
1

Je pense que c'est le plus facile.

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

ou ce qui est peut-être plus rapide car il ne crée pas de shell secondaire

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

Autres utilisations intéressantes

mélanger les lignes après la ligne d'en-tête

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

lignes inverses après la ligne d'en-tête

cat file.txt |  ( head -n 1 ; tac )
utilisateur2449151
la source
2
Voir unix.stackexchange.com/questions/11856/… . Ce n'est pas vraiment une bonne solution.
Wildcard
1
Ne fonctionne pas, cat file | { head -n 1 ; sort ; } > file2seulement montrer la tête
Peter Krauss
0
command | head -1; command | tail -n +2 | sort
Sarva
la source
4
Cela commence commanddeux fois. Par conséquent, il est limité à certaines commandes spécifiques. Cependant, pour la pscommande demandée dans l'exemple, cela fonctionnerait.
jofel
0

Simple et direct!

<command> | head -n 1; <command> | sed 1d | sort <....>
  • sed nd ---> 'n' spécifie le numéro de ligne et 'd' signifie delete.
Jatsui
la source
1
Comme jofel a commenté la réponse de Sarva il y a un an et demi, cela commence commanddeux fois. Donc, pas vraiment approprié pour une utilisation dans un pipeline.
Wildcard
0

Je suis venu ici à la recherche d'une solution pour la commande w . Cette commande affiche les détails de qui est connecté et de ce qu’ils font.

Pour afficher les résultats triés, mais avec les en-têtes conservés en haut (il y a 2 lignes d'en-têtes), je me suis installé sur:

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

Évidemment, cela lance la commande w deux fois et peut donc ne pas convenir à toutes les situations. Cependant, à son avantage, il est nettement plus facile à retenir.

Notez que le tail -n +3moyen "affiche toutes les lignes à partir de la 3ème" (voir les man taildétails).

Robert
la source
-2

Essayez de faire:

wc -l file_name | tail -n $(awk '{print $1-1}') file_name | sort
Barry
la source
3
Je ne comprends pas
Pierre.Vriens