Existe-t-il un moyen d'ignorer les lignes d'en-tête dans un tri UNIX?

102

J'ai un fichier à champ de largeur fixe que j'essaie de trier à l'aide de l'utilitaire de tri UNIX (Cygwin, dans mon cas).

Le problème est qu'il y a un en-tête de deux lignes en haut du fichier qui est trié en bas du fichier (car chaque ligne d'en-tête commence par un deux-points).

Existe-t-il un moyen de dire que le tri "passe les deux premières lignes non triées" ou de spécifier un ordre qui trie les lignes deux-points vers le haut - les lignes restantes commencent toujours par un numérique à 6 chiffres (qui est en fait la clé I 'm tri sur) si cela aide.

Exemple:

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

devrait trier:

:0:12345
:1:6:2:3:8:4:2
010005TSTDOG_FOOD01
222334NOTALINEOUT01
325611LVEANOTHERS00
477821USASHUTTLES21
500123TSTMY_RADAR00
Rob Gilliam
la source
Pour mémoire: la ligne de commande que j'utilise jusqu'à présent est "sort -t \\ -k1.1,1.6 <file>" [les données peuvent contenir des espaces, mais ne contiendront jamais de barre oblique inverse]
Rob Gilliam

Réponses:

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

Les parenthèses créent un sous-shell, encapsulant le stdout afin que vous puissiez le diriger ou le rediriger comme s'il provenait d'une seule commande.

BobS
la source
Merci; J'accepte cette réponse car elle semble la plus complète et la plus concise (et je comprends ce qu'elle fait!) - cela devrait être "head -n 2", cependant :-)
Rob Gilliam
1
Merci, j'ai corrigé la partie «tête».
BobS
4
Existe-t-il un moyen de faire fonctionner cette version sur les données canalisées? J'ai essayé avec tee >(head -n $header_size) | tail -n +$header_size | sort, mais la tête semble courir après le tail|sorttuyau, donc l'en-tête finit par être imprimé à la fin. Est-ce une condition déterministe ou raciale?
Damien Pollet
Vous pouvez probablement reconstituer quelque chose où vous utilisez catpour rediriger le stdin vers un fichier temporaire, puis exécuter la commande ci-dessus sur ce nouveau fichier, mais cela commence à devenir assez moche pour qu'il soit probablement préférable d'utiliser l'une des solutions basées sur awk données dans les autres réponses.
BobS
@DamienPollet: Voir la réponse de Dave .
Jonathan Leffler
63

Si cela ne vous dérange pas d'utiliser awk, vous pouvez profiter des awkcapacités de tuyau intégrées

par exemple.

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

Cela imprime les deux premières lignes textuellement et conduit le reste à travers sort.

Notez que cela présente l'avantage très spécifique de pouvoir trier sélectivement des parties d'une entrée canalisée. toutes les autres méthodes suggérées ne trieront que les fichiers simples qui peuvent être lus plusieurs fois. Cela fonctionne sur n'importe quoi.

Dave
la source
2
Très bien, et cela fonctionne avec des tuyaux arbitraires, pas seulement des fichiers!
lapo
4
Beau, awk ne cesse de me surprendre. De plus, vous n'avez pas besoin du $0, printc'est assez.
nachocab
1
La réponse de @SamWatkins freeseek est moins moche.
fasce.
Que fait l'option -r pour trier? Est-ce censé être un tri inversé?
gvrocha
32

Voici une version qui fonctionne sur les données canalisées:

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

Si votre en-tête comporte plusieurs lignes:

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

Cette solution est d' ici

freeseek
la source
9
agréable. pour le cas d'en-tête unique que j'utilise, extract_data | (read h; echo "$h"; sort) il est assez court pour se souvenir. votre exemple couvre plus de cas marginaux. :) C'est la meilleure réponse. fonctionne sur les tuyaux. pas de problème.
fasce.
1
Ok, j'ai stratifié ceci et il semble que bash fasse des efforts particuliers pour que cela fonctionne. En général, si vous codez cela en C ou dans un autre langage, cela ne fonctionnera pas car stdio lirait plus que la première ligne d'en-tête. Si vous l'exécutez sur un fichier pouvant être recherché, bash lit un morceau plus grand (128 octets dans mon test), puis revient après la fin de la première ligne. Si vous l'exécutez sur un tube, bash lit un caractère à la fois jusqu'à ce qu'il passe la fin de la ligne.
Sam Watkins
Agréable! Si vous voulez juste manger la tête, c'est encore plus facile à retenir:extract_data | (read; sort)
Jason Suárez
Celui-ci est presque parfait mais vous devez utiliser "IFS = read" au lieu de "read" pour conserver les espaces de début et de fin.
Stanislav German-Evtushenko
6
Cela devrait être la réponse acceptée à mon avis. Simple, concis et plus flexible en ce sens qu'il fonctionne également sur les données canalisées.
Paul I
12

Dans les cas simples, sedpeut faire le travail avec élégance:

    your_script | (sed -u 1q; sort)

ou équivalent,

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

La clé est dans la 1q- imprimer la première ligne (en-tête) et quitter (en laissant le reste de l'entrée à sort).

Pour l'exemple donné, 2qfera l'affaire.

Le -ucommutateur (sans tampon) est requis pour les seds (notamment les GNU) qui liraient autrement l'entrée en morceaux, consommant ainsi les données que vous souhaitez parcourir à la sortplace.

Andrea
la source
1
Salut, @Andrea; Bienvenue dans Stack Overflow. J'ai bien peur que votre réponse ne fonctionne pas, du moins pas lorsque je la teste dans Git Bash sous Windows (j'ai quitté Cygwin, le shell que j'utilisais il y a 6 ans). La commande sed extrait toutes les données du stdin, ne laissant aucune donnée à passer pour le tri. Essayez de changer la commande en cat your_data | (sed 1q; wc -l) pour voir ce que je veux dire.
Rob Gilliam
1
Cela pourrait fonctionner si vous transmettez l'entrée dans un second temps à la commande sed, comme ceci: cat sortMe.csv | (sed 1q sortMe.csv; sort -t, -k3 -rn)> sorted.csv
Harry Cramer
8

Vous pouvez utiliser tail -n +3 <file> | sort ...(tail affichera le contenu du fichier à partir de la 3ème ligne).

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

exemple:

> 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
la source
3

Cela ne prend que 2 lignes de code ...

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

Pour une donnée numérique, -n est obligatoire. Pour le tri alpha, l'option -n n'est pas obligatoire.

Exemple de fichier:
$ cat test.txt

header
8
5
100
1
-1

Résultat:
$ cat a.tmp

header
-1
1
5
8
100

Ian Sherbin
la source
1
N'est-ce pas fondamentalement la même réponse que la réponse acceptée? (Sauf que l'approche de BobS met le résultat sur stdout, vous permettant d'envoyer le résultat à travers d'autres filtres avant d'être écrit dans un fichier, si nécessaire)
Rob Gilliam
1

Voici donc une fonction bash où les arguments sont exactement comme le tri. Fichiers et tuyaux de soutien.

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
}

Comment ça fonctionne. Cette ligne vérifie s'il y a au moins un argument et si le dernier argument est un fichier.

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

Cela enregistre le fichier pour séparer l'argument. Puisque nous sommes sur le point d'effacer le dernier argument.

        local file=${@: -1}

Ici, nous supprimons le dernier argument. Puisque nous ne voulons pas le passer comme argument de tri.

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

Enfin, nous faisons la partie awk, en passant les arguments (moins le dernier argument s'il s'agissait du fichier) pour trier dans awk. Cela a été suggéré à l'origine par Dave et modifié pour prendre des arguments de tri. Nous comptons sur le fait que ce $filesera vide si nous sommes à la tuyauterie, donc ignoré.

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

Exemple d'utilisation avec un fichier séparé par des virgules.

$ 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
grippe
la source
0

Avec 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)
croisé
la source
suppose que Python est installé sur le système (le mien ne le fait pas)
Rob Gilliam
0

Voici une fonction shell bash dérivée des autres réponses. Il gère à la fois les fichiers et les tuyaux. Le premier argument est le nom du fichier ou «-» pour stdin. Les arguments restants sont passés au tri. Quelques exemples:

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

La fonction 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
la source
0

C'est la même chose que la réponse d'Ian Sherbin mais ma mise en œuvre est: -

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
la source
-4
cat file_name.txt | sed 1d | sort 

Cela fera ce que vous voulez.

Sathish G
la source
1) Cela ne supprime que la ligne d'en-tête et trie le reste, cela ne trie pas tout sous la ligne d'en-tête en laissant l'en-tête intact. 2) il supprime la première ligne uniquement, lorsque l'en-tête est en fait de deux lignes (lire la question). 3) Pourquoi utilisez-vous "cat nom_fichier.txt | sed 1d" alors que "sed 1d <nom_fichier.txt" ou même simplement "sed 1d nom_fichier.txt" a le même effet?
Rob Gilliam