unix - tête ET queue de fichier

131

Disons que vous avez un fichier txt, quelle est la commande pour afficher les 10 premières lignes et les 10 dernières lignes du fichier simultanément?

c'est-à-dire que si le fichier fait 200 lignes, visualisez les lignes 1-10 et 190-200 en une seule fois.

haut
la source
Que voulez-vous dire "en une seule fois"?
cnicutar
@cnicutar ie. ne pas aller dans le fichier head -10 en regardant les données , puis en allant séparément au fichier tail -10 et en regardant les données
toop
@toop Si vous voulez un vrai exemple de travail, voir stackoverflow.com/a/44849814/99834
sorin

Réponses:

208

Vous pouvez simplement:

(head; tail) < file.txt

Et si vous avez besoin d'utiliser des tuyaux pour une raison quelconque, alors comme ceci:

cat file.txt | (head; tail)

Remarque: imprimera des lignes dupliquées si le nombre de lignes dans file.txt est inférieur aux lignes de tête par défaut + lignes de queue par défaut.

Aleksandra Zalcman
la source
54
Strictement parlant, cela ne vous donne pas la queue du fichier d'origine, mais la queue du flux après heada consommé les 10 premières lignes du fichier. (Comparez cela avec head < file.txt; tail < file.txtun fichier de moins de 20 lignes). Juste un point très mineur à garder à l'esprit. (Mais toujours +1.)
chepner
15
Agréable. Si vous voulez un espace entre les parties tête et queue: (tête; écho; queue) <file.txt
Simon Hibbs
3
Curieux de savoir pourquoi / comment cela fonctionne.
Je l'ai
9
@nametal En fait, vous pourriez même ne pas en avoir autant. Bien qu'il n'affichehead que les 10 premières lignes de son entrée, il n'est pas garanti qu'il n'en ait pas consommé plus pour trouver la fin de la 10ème ligne, laissant moins d'entrée pour l' affichage. less
chepner
20
Désolé à dire, mais la réponse ne fonctionne que dans certains cas. seq 100 | (head; tail)me donne seulement 10 premiers chiffres. Ce n'est que sur une taille d'entrée beaucoup plus grande (comme seq 2000) que la queue reçoit une entrée.
modulaire du
18

ed est le standard text editor

$ echo -e '1+10,$-10d\n%p' | ed -s file.txt
kev
la source
2
Que faire si le fichier contient plus ou moins de 200 lignes? Et vous ne connaissez pas le nombre de lignes ab initio?
Paul
@Paul J'ai changé sedpoured
kev
14

Pour un flux pur (par exemple, la sortie d'une commande), vous pouvez utiliser 'tee' pour bifurquer le flux et envoyer un flux à la tête et un à la queue. Cela nécessite l'utilisation de la fonction '> (list)' de bash (+ / dev / fd / N):

( COMMAND | tee /dev/fd/3 | head ) 3> >( tail )

ou en utilisant / dev / fd / N (ou / dev / stderr) plus des sous-shell avec une redirection compliquée:

( ( seq 1 100 | tee /dev/fd/2 | head 1>&3 ) 2>&1 | tail ) 3>&1
( ( seq 1 100 | tee /dev/stderr | head 1>&3 ) 2>&1 | tail ) 3>&1

(Aucun de ceux-ci ne fonctionnera dans csh ou tcsh.)

Pour quelque chose avec un peu meilleur contrôle, vous pouvez utiliser cette commande perl:

COMMAND | perl -e 'my $size = 10; my @buf = (); while (<>) { print if $. <= $size; push(@buf, $_); if ( @buf > $size ) { shift(@buf); } } print "------\n"; print @buf;'
RantingNerd
la source
1
+1 pour la prise en charge du flux. Vous pouvez réutiliser stderr:COMMAND | { tee >(head >&2) | tail; } |& other_commands
jfs
2
btw, il casse pour les fichiers plus grands que la taille du tampon (8K sur mon système). cat >/dev/nullle corrige:COMMAND | { tee >(head >&2; cat >/dev/null) | tail; } |& other_commands
jfs
J'ai adoré la solution, mais après avoir joué pendant un an, j'ai remarqué que dans certains cas, la queue courait avant la tête ... il n'y a pas de commande garantie entre headet tailcommandes: \ ...
janvier
7
(sed -u 10q; echo ...; tail) < file.txt

Juste une autre variation sur le (head;tail)thème, mais en évitant le problème de remplissage initial du tampon pour les petits fichiers.

client
la source
4

head -10 file.txt; tail -10 file.txt

En dehors de cela, vous devrez écrire votre propre programme / script.

mah
la source
1
Sympa, je les ai toujours utilisés catet / headou tailsifflés, bon à savoir que je peux les utiliser individuellement!
Paul
Comment puis-je diriger ces 10 premiers + 10 derniers dans une autre commande?
jusqu'au
1
@Paul - avec 'your_program' comme wc -l, il renvoie 10 au lieu de 20
jusqu'au
3
ou, sans avoir à créer un sous-shell: { head file; tail file; } | prog(l'espacement à l'intérieur des accolades et le point-virgule de fin sont requis)
glenn jackman
1
Wow ... un vote défavorable pour avoir une réponse assez similaire à d'autres (mais horodatée avant eux) après presque deux ans, de la part de quelqu'un qui a choisi de ne pas publier pourquoi il a voté à la baisse. Agréable!
mah
4

Basé sur le commentaire de JF Sebastian :

cat file | { tee >(head >&3; cat >/dev/null) | tail; } 3>&1

De cette façon, vous pouvez traiter la première ligne et le reste différemment dans un même tube, ce qui est utile pour travailler avec des données CSV:

{ echo N; seq 3;} | { tee >(head -n1 | sed 's/$/*2/' >&3; cat >/dev/null) | tail -n+2 | awk '{print $1*2}'; } 3>&1
N * 2
2
4
6
modulaire
la source
3

le problème ici est que les programmes orientés flux ne connaissent pas à l'avance la longueur du fichier (car il se peut qu'il n'y en ait pas, s'il s'agit d'un vrai flux).

des outils comme tailtamponner les n dernières lignes vues et attendre la fin du flux, puis imprimer.

si vous voulez le faire en une seule commande (et le faire fonctionner avec n'importe quel décalage, et ne pas répéter les lignes si elles se chevauchent), vous devrez émuler ce comportement que j'ai mentionné.

essayez ce awk:

awk -v offset=10 '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' yourfile
Samus_
la source
il a besoin de plus de travail pour éviter les problèmes lorsque le décalage est plus grand que le fichier
Samus_
Oui, cela fonctionne avec une sortie canalisée, pas seulement des fichiers: a.out | awk -v ...
Camille Goudeseune
en effet :) mais c'est le comportement normal de awk, la plupart des programmes en ligne de commande fonctionnent sur stdin lorsqu'ils sont appelés sans arguments.
Samus_
1
Très proche du comportement souhaité mais il semble que pour <10 lignes, il ajoute de nouvelles lignes supplémentaires.
sorin
3

Il a fallu beaucoup de temps pour aboutir à cette solution qui semble être la seule à couvrir tous les cas d'utilisation (jusqu'à présent):

command | tee full.log | stdbuf -i0 -o0 -e0 awk -v offset=${MAX_LINES:-200} \
          '{
               if (NR <= offset) print;
               else {
                   a[NR] = $0;
                   delete a[NR-offset];
                   printf "." > "/dev/stderr"
                   }
           }
           END {
             print "" > "/dev/stderr";
             for(i=NR-offset+1 > offset ? NR-offset+1: offset+1 ;i<=NR;i++)
             { print a[i]}
           }'

Liste des fonctionnalités:

  • sortie live pour la tête (évidemment que pour la queue n'est pas possible)
  • pas d'utilisation de fichiers externes
  • barre de progression un point pour chaque ligne après MAX_LINES, très utile pour les tâches de longue durée.
  • barre de progression sur stderr, assurant que les points de progression sont séparés de la tête + la queue (très pratique si vous voulez pipe stdout)
  • évite un ordre de journalisation incorrect en raison de la mise en mémoire tampon (stdbuf)
  • évitez de dupliquer la sortie lorsque le nombre total de lignes est inférieur à head + tail.
Sorin
la source
2

Je cherchais cette solution depuis un moment. Je l'ai essayé moi-même avec sed, mais le problème de ne pas connaître au préalable la longueur du fichier / flux était insurmontable. De toutes les options disponibles ci-dessus, j'aime la solution awk de Camille Goudeseune. Il a noté que sa solution laissait des lignes vierges supplémentaires dans la sortie avec un ensemble de données suffisamment petit. Ici, je propose une modification de sa solution qui supprime les lignes supplémentaires.

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { a_count=0; for (i in a) {a_count++}; for (i=NR-a_count+1; i<=NR; i++) print a[i] }' ; }
Michael Blahay
la source
1

Eh bien, vous pouvez toujours les enchaîner. Comme tel, head fiename_foo && tail filename_foo. Si cela ne suffit pas, vous pouvez écrire vous-même une fonction bash dans votre fichier .profile ou dans tout fichier de connexion que vous utilisez:

head_and_tail() {
    head $1 && tail $1
}

Et, appelez plus tard à partir de votre invite du shell: head_and_tail filename_foo.

SRI
la source
1

10 premières lignes de file.ext, puis ses 10 dernières lignes:

cat file.ext | head -10 && cat file.ext | tail -10

10 dernières lignes du fichier, puis les 10 premières:

cat file.ext | tail -10 && cat file.ext | head -10

Vous pouvez également diriger la sortie ailleurs:

(cat file.ext | head -10 && cat file.ext | tail -10 ) | your_program

Paul
la source
5
Pourquoi utiliser cat alors que vous pouvez simplement appeler head -10 file.txt?
jstarek
Pouvez-vous rendre le nombre de lignes variable, donc l'appel est quelque chose comme: head_ tail (foo, m, n) - renvoyant les m et les n dernières lignes de texte?
ricardo
@ricardo qui impliquerait d'écrire un script bash qui prend 3 arguments et les transmet à tailet / headou à une fonction en l'aliasant.
Paul
1

J'ai écrit une application python simple pour ce faire: https://gist.github.com/garyvdm/9970522

Il gère les tubes (flux) ainsi que les fichiers.

Gary van der Merwe
la source
2
Il serait préférable d'afficher les parties pertinentes du code.
fedorqui 'SO arrêtez de nuire'
1

s'inspirant des idées ci-dessus (testé bash & zsh)

mais en utilisant un alias `` chapeau '' Head and Tails

alias hat='(head -5 && echo "^^^------vvv" && tail -5) < '


hat large.sql
zzapper
la source
0

Pourquoi ne pas l'utiliser sedpour cette tâche?

sed -n -e 1,+9p -e 190,+9p textfile.txt

aimer
la source
3
Cela fonctionne pour les fichiers de longueur connue, mais pas pour les fichiers dont la longueur est inconnue.
Kevin
0

Pour gérer les tubes (flux) ainsi que les fichiers, ajoutez ceci à votre fichier .bashrc ou .profile:

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' ; }

Alors vous pouvez non seulement

headtail 10 < file.txt

mais aussi

a.out | headtail 10

(Cela ajoute toujours de fausses lignes vides lorsque 10 dépasse la longueur de l'entrée, contrairement à l'ancien a.out | (head; tail). Merci, précédents répondants.)

Remarque: headtail 10non headtail -10.

Camille Goudeseune
la source
0

S'appuyant sur ce que @Samus_ a expliqué ici sur le fonctionnement de la commande de @Aleksandra Zalcman, cette variante est pratique lorsque vous ne pouvez pas repérer rapidement où la queue commence sans compter les lignes.

{ head; echo "####################\n...\n####################"; tail; } < file.txt

Ou si vous commencez à travailler avec autre chose que 20 lignes, un nombre de lignes peut même vous aider.

{ head -n 18; tail -n 14; } < file.txt | cat -n
Script Wolf
la source
0

Pour imprimer les 10 premières et les 10 dernières lignes d'un fichier, vous pouvez essayer ceci:

cat <(head -n10 file.txt) <(tail -n10 file.txt) | less

mariana.ft
la source
0
sed -n "1,10p; $(( $(wc -l ${aFile} | grep -oE "^[[:digit:]]+")-9 )),\$p" "${aFile}"

REMARQUE : la variable aFile contient le chemin d'accès complet du fichier .

mark_infinite
la source
0

Je dirais qu'en fonction de la taille du fichier, la lecture active de son contenu peut ne pas être souhaitable. Dans ce cas, je pense qu'un simple script shell devrait suffire.

Voici comment j'ai récemment géré cela pour un certain nombre de très gros fichiers CSV que j'analysais:

$ for file in *.csv; do echo "### ${file}" && head ${file} && echo ... && tail ${file} && echo; done

Cela imprime les 10 premières lignes et les 10 dernières lignes de chaque fichier, tout en imprimant également le nom du fichier et quelques points de suspension avant et après.

Pour un seul gros fichier, vous pouvez simplement exécuter ce qui suit pour le même effet:

$ head somefile.csv && echo ... && tail somefile.csv
Jitsusama
la source
0

Consomme stdin, mais simple et fonctionne pour 99% des cas d'utilisation

head_and_tail

#!/usr/bin/env bash
COUNT=${1:-10}
IT=$(cat /dev/stdin)
echo "$IT" | head -n$COUNT
echo "..."
echo "$IT" | tail -n$COUNT

exemple

$ seq 100 | head_and_tail 4
1
2
3
4
...
97
98
99
100
Brad Parks
la source