Commande Unix rapide pour afficher des lignes spécifiques au milieu d'un fichier?


207

Essayer de déboguer un problème avec un serveur et mon seul fichier journal est un fichier journal de 20 Go (sans horodatage même! Pourquoi les gens l'utilisent-ils System.out.println()comme journal ? En production?!)

En utilisant grep, j'ai trouvé une zone du fichier que j'aimerais consulter, ligne 347340107.

Autre que de faire quelque chose comme

head -<$LINENUM + 10> filename | tail -20 

... ce qui nécessiterait headde lire les 347 millions de premières lignes du fichier journal, existe-t-il une commande rapide et facile qui transfèrerait les lignes 347340100 - 347340200 (par exemple) vers la console?

mise à jour J'ai totalement oublié que grep peut imprimer le contexte autour d'un match ... cela fonctionne bien. Merci!


J'imagine que grep doit rechercher le fichier entier, il doit y avoir un moyen moins intensif de CPU pour le faire.
ojblass

Réponses:


70

avec GNU-grep, vous pourriez simplement dire

grep --context = 10 ...

7
Ou plus précisément 10 lignes avant: grep -B 10 ... Ou 10 lignes après: grep -A 10 ...
Boy Baukema

17
Cette commande ne fonctionne pas, sous sed -n '<start>, <end> p' fonctionne
Basav

5
Ce n'est en fait pas ce que vous voulez, car il traitera le fichier entier même si la correspondance est dans le bit supérieur. À ce stade, un combo tête / queue ou queue / tête est beaucoup plus efficace.
Sklivvz

3
Cela ne satisfait pas du tout la question posée car cela n'offre pas un moyen de sortir une ligne spécifique , comme demandé.
Chris Rasys

1
Ce n'est pas vraiment ce qui a été demandé. @matt b, pourquoi n'acceptez-vous pas cette réponse?
user1271772

391

J'ai trouvé deux autres solutions si vous connaissez le numéro de ligne mais rien d'autre (pas de grep possible):

En supposant que vous ayez besoin des lignes 20 à 40,

sed -n '20,40p;41q' file_name

ou

awk 'FNR>=20 && FNR<=40' file_name

6
+1: vous souhaiterez peut-être quitter après l'impression. Peut offrir des avantages en termes de performances si le fichier est vraiment énorme.
jaypal singh

awk 'NR> = 20 && NR <= 40' file_name
Sudipta Basak

2
sed -n '20, 40p; 41q 'nom_fichier pour quitter ensuite.
Snigdha Batra

1
spécifiquement, ce sont les numéros de ligne de début et de fin. Si vous êtes dans un fichier plus grand, ce sera '12345678,12345699p'
Code Abominator

1
En plus du commentaire de @ CodeAbominator, demandez 41qà sed de quitter la ligne 41.
Brice

116
# print line number 52
sed -n '52p' # method 1
sed '52!d' # method 2
sed '52q;d' # method 3,  efficient on large files 

méthode 3 efficace sur les fichiers volumineux

moyen le plus rapide d'afficher des lignes spécifiques


J'essaie de comprendre comment adapter la méthode 3 pour utiliser une plage au lieu d'une seule ligne, mais je crains que mon sed-foo ne soit pas à la hauteur.
Xiong Chiamiov

9
@XiongChiamiov Que diriez-vous de sed -n '1,500p; 501q' pour l'impression 1-500?
Sam

3
La raison pour laquelle les deux premières lignes / méthodes sont moins efficaces, c'est qu'elles continuent de traiter toutes les lignes après la ligne 52, jusqu'à la fin, tandis que le n ° 3 s'arrête après l'impression de la ligne 52.
flow2k

1
Cette réponse gagnerait à expliquer ce que font tous les arguments.
Bram Vanroy

25

Non, les fichiers ne sont pas adressables par ligne.

Il n'existe aucun moyen à temps constant pour trouver le début de la ligne n dans un fichier texte. Vous devez diffuser le fichier et compter les retours à la ligne.

Utilisez l'outil le plus simple / le plus rapide dont vous disposez pour faire le travail. Pour moi, utiliser headest beaucoup plus logique que grep, car ce dernier est beaucoup plus compliqué. Je ne dis pas " grepest lent", ce n'est vraiment pas le cas, mais je serais surpris si c'est plus rapide que headdans ce cas. Ce serait un bug head, en gros.


2
À moins que les lignes aient une largeur fixe en octets, vous ne savez pas où déplacer le pointeur de fichier sans compter les nouveaux caractères de ligne depuis le début du fichier.
Joseph Lust

Cela ne fournit pas de réponse à la question. Pour critiquer ou demander des éclaircissements à un auteur, laissez un commentaire sous son article.
exhuma

@exhuma Vous avez raison. J'ai réécrit. Il y a sept ans, je me suis vexé. :)
détendez-vous

20

Qu'en est-il de:

tail -n +347340107 filename | head -n 100

Je ne l'ai pas testé, mais je pense que cela fonctionnerait.


Non, la queue a généralement une limite de 256 derniers kilo-octets ou similaire, selon la version et le système d'exploitation.
Antti Rytsölä

💪 yessire miller
dctremblay

13

Je préfère juste entrer lesset

  • en tapant 50%pour aller à mi-chemin du fichier,
  • 43210G aller à la ligne 43210
  • :43210 Faire la même chose

et des trucs comme ça.

Encore mieux: appuyez sur vpour commencer l'édition (dans vim, bien sûr!), À cet endroit. Maintenant, notez qu'il vima les mêmes raccourcis clavier!


12

Je diviserais d'abord le fichier en quelques fichiers plus petits comme celui-ci

$ split --lines=50000 /path/to/large/file /path/to/output/file/prefix

puis grep sur les fichiers résultants.


d'accord, rompez cette connexion et créez un travail cron pour le faire correctement. utilisez logrotate ou quelque chose de similaire pour les empêcher de devenir si énormes.
Tanj

9

Vous pouvez utiliser la excommande, un éditeur Unix standard (qui fait maintenant partie de Vim), par exemple

  • afficher une seule ligne (par exemple 2ème ligne):

    ex +2p -scq file.txt

    syntaxe sed correspondante: sed -n '2p' file.txt

  • gamme de lignes (par exemple 2-5 lignes):

    ex +2,5p -scq file.txt

    syntaxe sed: sed -n '2,5p' file.txt

  • de la ligne donnée jusqu'à la fin (par ex. du 5e à la fin du fichier):

    ex +5,p -scq file.txt

    syntaxe sed: sed -n '2,$p' file.txt

  • plusieurs gammes de lignes (par exemple 2-4 et 6-8 lignes):

    ex +2,4p +6,8p -scq file.txt

    syntaxe sed: sed -n '2,4p;6,8p' file.txt

Les commandes ci-dessus peuvent être testées avec le fichier de test suivant:

seq 1 20 > file.txt

Explication:

  • +ou -csuivi de la commande - exécutez la commande (vi / vim) après la lecture du fichier,
  • -s - mode silencieux, utilise également le terminal actuel comme sortie par défaut,
  • qsuivi de -cla commande pour quitter l'éditeur (ajouter !pour forcer la fermeture, par exemple -scq!).

7

Si votre numéro de ligne est 100 à lire

head -100 filename | tail -1

6

Avoir ack

Installation d'Ubuntu / Debian:

$ sudo apt-get install ack-grep

Exécutez ensuite:

$ ack --lines=$START-$END filename

Exemple:

$ ack --lines=10-20 filename

De $ man ack:

--lines=NUM
    Only print line NUM of each file. Multiple lines can be given with multiple --lines options or as a comma separated list (--lines=3,5,7). --lines=4-7 also works. 
    The lines are always output in ascending order, no matter the order given on the command line.

1
Cela me semble être la commande avec la syntaxe la plus intuitive de toutes les réponses ici.
nzn le

Depuis la version 2.999_06 du 10 janvier 2019, le --linesparamètre a été supprimé.
burny

4

sed devra également lire les données pour compter les lignes. La seule façon dont un raccourci serait possible serait qu'il y ait un contexte / ordre dans le fichier pour fonctionner. Par exemple, s'il y avait des lignes de journal précédées d'une heure / date fixe, etc., vous pouvez utiliser l' utilitaire look unix pour rechercher binaire dans les fichiers des dates / heures particulières


4

Utilisation

x=`cat -n <file> | grep <match> | awk '{print $1}'`

Ici, vous obtiendrez le numéro de ligne où la correspondance s'est produite.

Vous pouvez maintenant utiliser la commande suivante pour imprimer 100 lignes

awk -v var="$x" 'NR>=var && NR<=var+100{print}' <file>

ou vous pouvez également utiliser "sed"

sed -n "${x},${x+100}p" <file>

Si vous avez plus d'une correspondance, utilisez: "awk 'NR == 1 {print $ 1}" pour la première correspondance et ainsi de suite
Ramana Reddy

2

Avec sed -e '1,N d; M q'vous imprimerez les lignes N + 1 à M. C'est probablement un peu mieux grep -Ccar il n'essaie pas de faire correspondre les lignes à un motif.


-eest facultatif ici.
flow2k

2

En s'appuyant sur la réponse de Sklivvz, voici une fonction intéressante que l'on peut mettre dans un .bash_aliasesfichier. Il est efficace sur les fichiers volumineux lors de l'impression de trucs à l'avant du fichier.

function middle()
{
    startidx=$1
    len=$2
    endidx=$(($startidx+$len))
    filename=$3

    awk "FNR>=${startidx} && FNR<=${endidx} { print NR\" \"\$0 }; FNR>${endidx} { print \"END HERE\"; exit }" $filename
}

1

Pour afficher une ligne de a <textfile>par son <line#>, procédez comme suit:

perl -wne 'print if $. == <line#>' <textfile>

Si vous voulez un moyen plus puissant pour montrer une gamme de lignes avec des expressions régulières - je ne dirai pas pourquoi grep est une mauvaise idée pour cela, cela devrait être assez évident - cette expression simple vous montrera votre gamme dans un passe unique qui est ce que vous voulez lorsque vous traitez avec ~ 20 Go de fichiers texte:

perl -wne 'print if m/<regex1>/ .. m/<regex2>/' <filename>

(astuce: si votre regex en contient /, utilisez quelque chose comme à la m!<regex>!place)

Cela imprimerait à <filename>partir de la ligne qui correspond <regex1>jusqu'à (et y compris) la ligne qui correspond <regex2>.

Il ne faut pas un assistant pour voir comment quelques ajustements peuvent le rendre encore plus puissant.

Dernière chose: perl, car c'est un langage mature, a de nombreuses améliorations cachées pour favoriser la vitesse et les performances. Dans cette optique, cela en fait le choix évident pour une telle opération, car il a été initialement développé pour gérer de gros fichiers journaux, du texte, des bases de données, etc.


vraiment, cela ne me semble pas comme ça, car quand exécute une commande perl plus compliquée que de dire, exécuter 2+ programmes piped ensemble (plus bas sur la page), et, je pense que vous dites en fait parce que j'ai tapé plus de une explication qui vous obligeait à LIRE, car il y a tout aussi complexe (ou plus) en bas de la page qui n'a pas été soufflé hors de l'eau ...
sheesh

Notez que l'utilisateur a demandé une gamme de lignes - votre exemple peut cependant être trivialement adapté.
Sklivvz

0

Vous pouvez essayer cette commande:

egrep -n "*" <filename> | egrep "<line number>"

0

Facile avec perl! Si vous voulez obtenir les lignes 1, 3 et 5 d'un fichier, dites / etc / passwd:

perl -e 'while(<>){if(++$l~~[1,3,5]){print}}' < /etc/passwd

1
Vous dites que c'est facile avec awk, mais vous l'avez plutôt fait en perl?
Prisonnier 13

0

Je suis surpris qu'une seule autre réponse (par Ramana Reddy) ait suggéré d'ajouter des numéros de ligne à la sortie. Ce qui suit recherche le numéro de ligne requis et colore la sortie.

file=FILE
lineno=LINENO
wb="107"; bf="30;1"; rb="101"; yb="103"
cat -n ${file} | { GREP_COLORS="se=${wb};${bf}:cx=${wb};${bf}:ms=${rb};${bf}:sl=${yb};${bf}" grep --color -C 10 "^[[:space:]]\\+${lineno}[[:space:]]"; }

Les réponses avec code ont tendance à être signalées pour suppression. Pourriez-vous ajouter quelques commentaires sur la façon dont cela résout le problème?
Graham
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.