Outil Bash pour obtenir la nième ligne d'un fichier

606

Existe-t-il une manière "canonique" de procéder? J'utilise head -n | tail -1ce qui fait l'affaire, mais je me demande s'il existe un outil Bash qui extrait spécifiquement une ligne (ou une plage de lignes) d'un fichier.

Par "canonique", j'entends un programme dont la fonction principale est de faire cela.

Vlad Vivdovitch
la source
10
La "méthode Unix" consiste à enchaîner des outils qui font bien leur travail respectif. Je pense donc que vous avez déjà trouvé une méthode très appropriée. D'autres méthodes incluent awket sedet je suis sûr que quelqu'un peut également proposer un Perl one-liner;)
0xC0000022L
3
La double commande suggère que la head | tailsolution n'est pas optimale. D'autres solutions plus optimales ont été suggérées.
Jonathan Leffler
Avez-vous effectué des tests de performances sur la solution la plus rapide pour un cas moyen?
Marcin
5
Repères (pour une plage) à la ligne de chat X à la ligne Y sur un énorme fichier sur Unix et Linux . (cc @Marcin, au cas où vous vous poseriez encore des questions après deux ans et plus)
Kevin
6
La head | tailsolution ne fonctionne pas, si vous interrogez une ligne qui n'existe pas dans l'entrée: elle imprimera la dernière ligne.
jarno

Réponses:

803

headet le tuyau avec tailsera lent pour un énorme fichier. Je suggérerais sedceci:

sed 'NUMq;d' file

NUMest le numéro de la ligne que vous souhaitez imprimer; ainsi, par exemple, sed '10q;d' filepour imprimer la 10e ligne de file.

Explication:

NUMqquittera immédiatement lorsque le numéro de ligne est NUM.

dsupprimera la ligne au lieu de l'imprimer; cela est inhibé sur la dernière ligne car le qfait que le reste du script soit ignoré lors de la fermeture.

Si vous avez NUMdans une variable, vous voudrez utiliser des guillemets doubles au lieu de simples:

sed "${NUM}q;d" file
anubhava
la source
44
Pour ceux qui se demandent, cette solution semble environ 6 à 9 fois plus rapide que les sed -n 'NUMp'et sed 'NUM!d'solutions proposées ci - dessous.
Skippy le Grand Gourou
75
Je pense que ce tail -n+NUM file | head -n1sera probablement aussi rapide ou plus rapide. Au moins, c'était (significativement) plus rapide sur mon système quand je l'ai essayé avec NUM étant 250000 sur un fichier avec un demi-million de lignes. YMMV, mais je ne vois pas vraiment pourquoi.
rici
2
@rici (révision du commentaire précédent) Sous Linux (Ubuntu 12.04, Fedora 20), l'utilisation catest en effet plus rapide (presque deux fois plus rapide), mais uniquement si le fichier n'a pas encore été mis en cache . Une fois le fichier mis en cache , l'utilisation directe de l'argument du nom de fichier est plus rapide (environ 1/3 plus rapide), tandis que les catperformances restent les mêmes. Curieusement, sous OS X 10.9.3 rien de tout cela ne semble faire de différence: cat/ non cat, fichier mis en cache ou non. @anubhava: mon plaisir.
mklement0
2
@SkippyleGrandGourou: Étant donné la nature spécifique de cette optimisation , même vos plages de nombres sont inutiles en tant que déclaration générale . La seule conclusion générale est la suivante: (a) cette optimisation peut être appliquée en toute sécurité à toutes les entrées, (b) les effets varieront de zéro à dramatique , selon l'indice de la ligne recherchée par rapport au nombre de lignes globales.
mklement0
17
sed 'NUMqaffichera les premiers NUMfichiers et ;dsupprimera tout sauf la dernière ligne.
anubhava
304
sed -n '2p' < file.txt

imprime la 2e ligne

sed -n '2011p' < file.txt

2011e ligne

sed -n '10,33p' < file.txt

ligne 10 jusqu'à ligne 33

sed -n '1p;3p' < file.txt

1ère et 3ème ligne

etc...

Pour ajouter des lignes avec sed, vous pouvez vérifier ceci:

sed: insérer une ligne dans une certaine position

jm666
la source
6
@RafaelBarbosa <dans ce cas n'est pas nécessaire. Simplement, c'est ma préférence en utilisant des redirections, car j'ai souvent utilisé des redirections comme sed -n '100p' < <(some_command)- donc, la syntaxe universelle :). Ce n'est PAS moins efficace, car la redirection se fait avec shell lors de la fourche, donc ... ce n'est qu'une préférence ... (et oui, c'est un caractère de plus) :)
jm666
1
@ jm666 En fait, il y a 2 caractères de plus car vous mettriez normalement le '<' ainsi qu'un espace supplémentaire '' après <comme opposé à un seul espace si vous n'aviez pas utilisé le <:)
rasen58
2
@ rasen58 l'espace est aussi un personnage? :) / ok, je plaisante
tu as
1
@duhaime bien sûr, si quelqu'un doit faire des optimisations. Mais à mon humble avis pour les problèmes "courants", c'est correct et la différence est imperceptible. De plus, le head/ tailne résout pas le sed -n '1p;3p'scénario - alias imprimer plus de lignes non adjacentes ...
jm666
1
@duhaime bien sûr - la note est correcte et nécessaire. :)
jm666
93

J'ai une situation unique où je peux comparer les solutions proposées sur cette page, et donc j'écris cette réponse comme une consolidation des solutions proposées avec des temps d'exécution inclus pour chacune.

Installer

J'ai un fichier de données texte ASCII de 3,261 gigaoctets avec une paire clé-valeur par ligne. Le fichier contient 3 339 550 350 lignes au total et ne peut pas être ouvert dans n'importe quel éditeur que j'ai essayé, y compris mon go-to Vim. J'ai besoin de sous-définir ce fichier afin d'étudier certaines des valeurs que j'ai découvertes ne commencent qu'aux alentours de la ligne ~ 500 000 000.

Parce que le fichier contient tellement de lignes:

  • Je dois extraire uniquement un sous-ensemble des lignes pour faire quelque chose d'utile avec les données.
  • La lecture de chaque ligne menant aux valeurs qui m'intéressent va prendre beaucoup de temps.
  • Si la solution lit au-delà des lignes auxquelles je tiens et continue à lire le reste du fichier, elle perdra du temps à lire près de 3 milliards de lignes non pertinentes et prendra 6 fois plus de temps que nécessaire.

Mon meilleur scénario est une solution qui extrait une seule ligne du fichier sans lire aucune des autres lignes du fichier, mais je ne peux pas penser à la façon dont j'accomplirais cela dans Bash.

Aux fins de ma raison, je n'essaierai pas de lire les 500 000 000 lignes dont j'avais besoin pour mon propre problème. Au lieu de cela, j'essaierai d'extraire la ligne 50 000 000 sur 3 339 550 350 (ce qui signifie que la lecture du fichier complet prendra 60 fois plus de temps que nécessaire).

J'utiliserai le timeintégré pour comparer chaque commande.

Référence

Voyons d'abord comment la head tailsolution:

$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0

real    1m15.321s

La ligne de base pour la ligne 50 millions est 00: 01: 15.321, si j'étais allé directement pour la ligne 500 millions, ce serait probablement ~ 12,5 minutes.

Couper

Je doute de celui-ci, mais ça vaut le coup:

$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0

real    5m12.156s

Celui-ci a pris 00: 05: 12.156 pour fonctionner, ce qui est beaucoup plus lent que la ligne de base! Je ne sais pas s'il a lu l'intégralité du fichier ou juste jusqu'à 50 millions de lignes avant de s'arrêter, mais malgré cela, cela ne semble pas être une solution viable au problème.

AWK

J'ai uniquement exécuté la solution avec le exitcar je n'allais pas attendre que le fichier complet s'exécute:

$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0

real    1m16.583s

Ce code a fonctionné en 00: 01: 16.583, ce qui est seulement ~ 1 seconde plus lent, mais toujours pas une amélioration par rapport à la ligne de base. À ce rythme, si la commande exit avait été exclue, il aurait probablement fallu environ 76 minutes pour lire l'intégralité du fichier!

Perl

J'ai également exécuté la solution Perl existante:

$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0

real    1m13.146s

Ce code a fonctionné en 00: 01: 13.146, ce qui est ~ 2 secondes plus rapide que la ligne de base. Si je l'exécutais sur 500 000 000, cela prendrait probablement environ 12 minutes.

sed

La meilleure réponse au tableau, voici mon résultat:

$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0

real    1m12.705s

Ce code a fonctionné en 00: 01: 12.705, ce qui est 3 secondes plus rapide que la ligne de base et ~ 0,4 seconde plus rapide que Perl. Si je l'avais exécuté sur les 500 000 000 lignes, cela aurait probablement pris environ 12 minutes.

mapfile

J'ai bash 3.1 et ne peux donc pas tester la solution mapfile.

Conclusion

Il semble que, pour la plupart, il est difficile d'améliorer la head tailsolution. Au mieux, la sedsolution offre une augmentation de ~ 3% de l'efficacité.

(pourcentages calculés avec la formule % = (runtime/baseline - 1) * 100)

Ligne 50 000 000

  1. 00: 01: 12.705 (-00: 00: 02.616 = -3.47%) sed
  2. 00: 01: 13.146 (-00: 00: 02.175 = -2.89%) perl
  3. 00: 01: 15.321 (+00: 00: 00.000 = + 0,00%) head|tail
  4. 00: 01: 16.583 (+00: 00: 01.262 = + 1.68%) awk
  5. 00: 05: 12.156 (+00: 03: 56.835 = + 314.43%) cut

Ligne 500 000 000

  1. 00: 12: 07.050 (-00: 00: 26.160) sed
  2. 00: 12: 11.460 (-00: 00: 21.750) perl
  3. 00: 12: 33.210 (+00: 00: 00.000) head|tail
  4. 00: 12: 45.830 (+00: 00: 12.620) awk
  5. 00: 52: 01.560 (+00: 40: 31.650) cut

Ligne 3,338,559,320

  1. 01: 20: 54.599 (-00: 03: 05.327) sed
  2. 01: 21: 24.045 (-00: 02: 25.227) perl
  3. 01: 23: 49.273 (+00: 00: 00.000) head|tail
  4. 01: 25: 13.548 (+00: 02: 35.735) awk
  5. 05: 47: 23.026 (+04: 24: 26.246) cut
CaféineConnoisseur
la source
4
Je me demande combien de temps prendrait simplement le fichier entier dans / dev / null. (Et si c'était seulement une référence de disque dur?)
sanmai
Je ressens une envie perverse de m'incliner devant votre propriété d'un dictionnaire de fichiers texte de 3+ gig. Quelle que soit la justification, cela embrasse donc la textualité :)
Stabledog
51

Avec awkc'est assez rapide:

awk 'NR == num_line' file

Lorsque cela est vrai, le comportement par défaut awkest effectué: {print $0}.


Versions alternatives

Si votre fichier est volumineux, vous feriez mieux exitaprès avoir lu la ligne requise. De cette façon, vous économisez du temps CPU Voir la comparaison du temps à la fin de la réponse .

awk 'NR == num_line {print; exit}' file

Si vous souhaitez donner le numéro de ligne d'une variable bash, vous pouvez utiliser:

awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file   # equivalent

Voyez combien de temps est économisé en utilisant exit, spécialement si la ligne se trouve dans la première partie du fichier:

# Let's create a 10M lines file
for ((i=0; i<100000; i++)); do echo "bla bla"; done > 100Klines
for ((i=0; i<100; i++)); do cat 100Klines; done > 10Mlines

$ time awk 'NR == 1234567 {print}' 10Mlines
bla bla

real    0m1.303s
user    0m1.246s
sys 0m0.042s
$ time awk 'NR == 1234567 {print; exit}' 10Mlines
bla bla

real    0m0.198s
user    0m0.178s
sys 0m0.013s

La différence est donc de 0,198s contre 1,303s, environ 6 fois plus rapide.

fedorqui 'SO arrête de nuire'
la source
Cette méthode sera toujours plus lente car awk tente de diviser le champ. Les frais généraux de division du champ peuvent être réduits parawk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
kvantour
La puissance réelle de awk dans cette méthode sort quand vous voulez ligne concatenate n1 de fichier1, fichier2 de n2, n3 ou file3 ... awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3. Avec GNU awk, cela peut être accéléré en utilisant awk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3.
kvantour
@kvantour en effet, le prochain fichier de GNU awk est idéal pour de telles choses. Comment se FS=RSfait-il que le champ ne soit pas divisé?
fedorqui 'SO arrête de nuire'
1
FS=RSn'évite pas le fractionnement de champs, mais il n'analyse que les $ 0 et RS$0
n'affecte
@kvantour J'ai fait quelques tests avec FS=RSet je n'ai pas vu de différence sur les horaires. Et si je posais une question à ce sujet pour que vous puissiez vous développer? Merci!
fedorqui 'SO arrêtez de nuire'
29

Selon mes tests, en termes de performances et de lisibilité ma recommandation est:

tail -n+N | head -1

Nest le numéro de ligne que vous souhaitez. Par exemple, tail -n+7 input.txt | head -1imprime la 7e ligne du fichier.

tail -n+Nimprimera tout à partir de la ligne Net head -1l'arrêtera après une ligne.


L'alternative head -N | tail -1est peut-être légèrement plus lisible. Par exemple, cela imprimera la 7e ligne:

head -7 input.txt | tail -1

En ce qui concerne les performances, il n'y a pas beaucoup de différence pour les petites tailles, mais il sera surperformé par le tail | head (vu ci-dessus) lorsque les fichiers deviennent volumineux.

Le plus voté sed 'NUMq;d' est intéressant à savoir, mais je dirais qu'il sera compris par moins de personnes hors de la boîte que la solution tête / queue et il est également plus lent que queue / tête.

Dans mes tests, les deux versions queues / têtes ont surperformé sed 'NUMq;d' cohérente. Cela correspond aux autres repères qui ont été affichés. Il est difficile de trouver un cas où les queues / têtes étaient vraiment mauvaises. Ce n'est pas non plus surprenant, car ce sont des opérations que vous attendez à être fortement optimisées dans un système Unix moderne.

Pour avoir une idée des différences de performances, voici le nombre que j'obtiens pour un énorme fichier (9.3G):

  • tail -n+N | head -1: 3,7 secondes
  • head -N | tail -1: 4,6 secondes
  • sed Nq;d: 18,8 secondes

Les résultats peuvent différer, mais les performances head | tailet tail | headsont, en général, comparables pour les entrées plus petites, et sedsont toujours plus lents d'un facteur significatif (environ 5x ou plus).

Pour reproduire mon benchmark, vous pouvez essayer ce qui suit, mais soyez averti qu'il créera un fichier 9.3G dans le répertoire de travail actuel:

#!/bin/bash
readonly file=tmp-input.txt
readonly size=1000000000
readonly pos=500000000
readonly retries=3

seq 1 $size > $file
echo "*** head -N | tail -1 ***"
for i in $(seq 1 $retries) ; do
    time head "-$pos" $file | tail -1
done
echo "-------------------------"
echo
echo "*** tail -n+N | head -1 ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time tail -n+$pos $file | head -1
done
echo "-------------------------"
echo
echo "*** sed Nq;d ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time sed $pos'q;d' $file
done
/bin/rm $file

Voici la sortie d'un run sur ma machine (ThinkPad X1 Carbon avec un SSD et 16G de mémoire). Je suppose que lors de l'exécution finale, tout proviendra du cache, pas du disque:

*** head -N | tail -1 ***
500000000

real    0m9,800s
user    0m7,328s
sys     0m4,081s
500000000

real    0m4,231s
user    0m5,415s
sys     0m2,789s
500000000

real    0m4,636s
user    0m5,935s
sys     0m2,684s
-------------------------

*** tail -n+N | head -1 ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:49 tmp-input.txt
500000000

real    0m6,452s
user    0m3,367s
sys     0m1,498s
500000000

real    0m3,890s
user    0m2,921s
sys     0m0,952s
500000000

real    0m3,763s
user    0m3,004s
sys     0m0,760s
-------------------------

*** sed Nq;d ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:50 tmp-input.txt
500000000

real    0m23,675s
user    0m21,557s
sys     0m1,523s
500000000

real    0m20,328s
user    0m18,971s
sys     0m1,308s
500000000

real    0m19,835s
user    0m18,830s
sys     0m1,004s
Philipp Claßen
la source
1
Les performances sont-elles différentes entre head | tailvs tail | head? Ou cela dépend-il de la ligne imprimée (début du fichier vs fin du fichier)?
wisbucky
1
@wisbucky Je n'ai pas de chiffres précis, mais un inconvénient de la première utilisation de la queue suivie d'une "tête -1" est que vous devez connaître la longueur totale à l'avance. Si vous ne le connaissez pas, vous devrez d'abord le compter, ce qui sera une perte en termes de performances. Un autre inconvénient est qu'il est moins intuitif à utiliser. Par exemple, si vous avez le numéro 1 à 10 et que vous voulez obtenir la 3ème ligne, vous devrez utiliser "tail -8 | head -1". C'est plus sujet aux erreurs que "head -3 | tail -1".
Philipp Claßen
désolé, j'aurais dû inclure un exemple pour être clair. head -5 | tail -1vs tail -n+5 | head -1. En fait, j'ai trouvé une autre réponse qui a fait une comparaison de tests et s'est avérée tail | headplus rapide. stackoverflow.com/a/48189289
wisbucky
1
@wisbucky Merci de l'avoir mentionné! J'ai fait quelques tests et je dois admettre que c'était toujours un peu plus rapide, indépendamment de la position de la ligne par rapport à ce que j'ai vu. Compte tenu de cela, j'ai changé ma réponse et j'ai également inclus la référence au cas où quelqu'un voudrait la reproduire.
Philipp Claßen
27

Wow, toutes les possibilités!

Essaye ça:

sed -n "${lineNum}p" $file

ou l'un d'eux en fonction de votre version d'Awk:

awk  -vlineNum=$lineNum 'NR == lineNum {print $0}' $file
awk -v lineNum=4 '{if (NR == lineNum) {print $0}}' $file
awk '{if (NR == lineNum) {print $0}}' lineNum=$lineNum $file

( Vous devrez peut-être essayer la commande nawkougawk ).

Existe-t-il un outil qui imprime uniquement cette ligne particulière? Pas l'un des outils standard. Cependant, sedc'est probablement le plus proche et le plus simple à utiliser.

David W.
la source
21

Cette question étant étiquetée Bash, voici la façon de faire Bash (≥4): utilisez mapfileavec les options -s(skip) et -n(count).

Si vous avez besoin d'obtenir la 42ème ligne d'un fichier file:

mapfile -s 41 -n 1 ary < file

À ce stade, vous aurez un tableau dont aryles champs contiennent les lignes de file(y compris la nouvelle ligne de fin), où nous avons ignoré les 41 premières lignes ( -s 41) et nous nous sommes arrêtés après avoir lu une ligne ( -n 1). Voilà donc vraiment la 42e ligne. Pour l'imprimer:

printf '%s' "${ary[0]}"

Si vous avez besoin d'une gamme de lignes, dites la gamme 42–666 (inclus), dites que vous ne voulez pas faire le calcul vous-même et imprimez-les sur stdout:

mapfile -s $((42-1)) -n $((666-42+1)) ary < file
printf '%s' "${ary[@]}"

Si vous devez également traiter ces lignes, il n'est pas vraiment pratique de stocker la nouvelle ligne de fin. Dans ce cas, utilisez l' -toption (trim):

mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file
# do stuff
printf '%s\n' "${ary[@]}"

Vous pouvez demander à une fonction de le faire pour vous:

print_file_range() {
    # $1-$2 is the range of file $3 to be printed to stdout
    local ary
    mapfile -s $(($1-1)) -n $(($2-$1+1)) ary < "$3"
    printf '%s' "${ary[@]}"
}

Pas de commandes externes, seulement des builds Bash!

gniourf_gniourf
la source
11

Vous pouvez également utiliser sed print et quitter:

sed -n '10{p;q;}' file   # print line 10
bernd
la source
6
L' -noption désactive l'action par défaut pour imprimer chaque ligne, comme vous l'auriez sûrement découvert en regardant rapidement la page de manuel.
tripleee
Dans GNU, sed toutes les sedréponses sont à peu près à la même vitesse. Par conséquent (pour GNU sed ), c'est la meilleure sedréponse, car cela gagnerait du temps pour les gros fichiers et les petites valeurs de nième ligne .
agc
7

Vous pouvez également utiliser Perl pour cela:

perl -wnl -e '$.== NUM && print && exit;' some.file
Timofey Stolbov
la source
6

La solution la plus rapide pour les gros fichiers est toujours tail | head, à condition que les deux distances:

  • du début du fichier à la ligne de départ. Permet de l'appelerS
  • la distance entre la dernière ligne et la fin du fichier. Que ce soitE

sont connus. Ensuite, nous pourrions utiliser ceci:

mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"

combien est juste le nombre de lignes requises.

Un peu plus de détails dans https://unix.stackexchange.com/a/216614/79743

Communauté
la source
1
Veuillez clarifier les unités de Set E, (c.-à-d. Octets, caractères ou lignes).
agc
6

Toutes les réponses ci-dessus répondent directement à la question. Mais voici une solution moins directe mais une idée potentiellement plus importante, pour provoquer la réflexion.

Les longueurs de ligne étant arbitraires, tous les octets du fichier avant la nième ligne doivent être lus. Si vous avez un fichier volumineux ou si vous devez répéter cette tâche plusieurs fois et que ce processus prend du temps, vous devriez sérieusement réfléchir à la question de savoir si vous devez stocker vos données d'une manière différente en premier lieu.

La vraie solution est d'avoir un index, par exemple au début du fichier, indiquant les positions où commencent les lignes. Vous pouvez utiliser un format de base de données ou simplement ajouter une table au début du fichier. Vous pouvez également créer un fichier d'index distinct pour accompagner votre gros fichier texte.

Par exemple, vous pouvez créer une liste de positions de caractères pour les retours à la ligne:

awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx

puis lisez avec tail, qui seeks en fait directement au point approprié dans le fichier!

par exemple pour obtenir la ligne 1000:

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
  • Cela peut ne pas fonctionner avec des caractères à 2 octets / multi-octets, car awk est "sensible aux caractères" mais pas la queue.
  • Je n'ai pas testé cela contre un gros fichier.
  • Voir également cette réponse .
  • Alternativement - divisez votre fichier en fichiers plus petits!
Sanjay Manohar
la source
5

Pour faire suite à la réponse très utile de CaffeineConnoisseur en matière de benchmarking ... J'étais curieux de savoir à quelle vitesse la méthode `` mapfile '' était comparée à d'autres (car cela n'a pas été testé), j'ai donc essayé moi-même une comparaison de vitesse rapide et sale en tant que J'ai bash 4 à portée de main. J'ai lancé un test de la méthode "tail | head" (plutôt que head | tail) mentionnée dans l'un des commentaires sur la réponse du haut pendant que j'y étais, car les gens chantent ses louanges. Je n'ai rien de la taille du fichier de test utilisé; le meilleur que j'ai pu trouver à court terme était un fichier généalogique de 14 millions (longues lignes séparées par des espaces, un peu moins de 12 000 lignes).

Version courte: mapfile apparaît plus rapidement que la méthode cut, mais plus lent que tout le reste, donc je l'appellerais un raté. queue | head, OTOH, semble être le plus rapide, bien qu'avec un fichier de cette taille, la différence n'est pas si importante par rapport à sed.

$ time head -11000 [filename] | tail -1
[output redacted]

real    0m0.117s

$ time cut -f11000 -d$'\n' [filename]
[output redacted]

real    0m1.081s

$ time awk 'NR == 11000 {print; exit}' [filename]
[output redacted]

real    0m0.058s

$ time perl -wnl -e '$.== 11000 && print && exit;' [filename]
[output redacted]

real    0m0.085s

$ time sed "11000q;d" [filename]
[output redacted]

real    0m0.031s

$ time (mapfile -s 11000 -n 1 ary < [filename]; echo ${ary[0]})
[output redacted]

real    0m0.309s

$ time tail -n+11000 [filename] | head -n1
[output redacted]

real    0m0.028s

J'espère que cela t'aides!

Jo Valentine-Cooper
la source
4

En utilisant ce que d'autres ont mentionné, je voulais que ce soit une fonction rapide et dandy dans mon shell bash.

Créez un fichier: ~/.functions

Ajoutez-y le contenu:

getline() { line=$1 sed $line'q;d' $2 }

Ajoutez ensuite ceci à votre ~/.bash_profile:

source ~/.functions

Maintenant, lorsque vous ouvrez une nouvelle fenêtre bash, vous pouvez simplement appeler la fonction comme suit:

getline 441 myfile.txt

Mark Shust à M.academy
la source
3

Si vous avez plusieurs lignes délimitées par \ n (normalement une nouvelle ligne). Vous pouvez également utiliser «couper»:

echo "$data" | cut -f2 -d$'\n'

Vous obtiendrez la 2ème ligne du fichier. -f3vous donne la 3ème ligne.

danger89
la source
1
Peut également être utilisé pour afficher plusieurs lignes: cat FILE | cut -f2,5 -d$'\n'affichera les lignes 2 et 5 du FICHIER. (Mais cela ne préservera pas l'ordre.)
Andriy Makukha
2

Pour imprimer la nième ligne en utilisant sed avec une variable comme numéro de ligne:

a=4
sed -e $a'q:d' file

Ici, l'indicateur «-e» sert à ajouter un script à la commande à exécuter.

aliasav
la source
2
Le deux-points est une erreur de syntaxe et doit être un point-virgule.
tripleee
2

Beaucoup de bonnes réponses déjà. Personnellement, je vais avec awk. Pour plus de commodité, si vous utilisez bash, ajoutez simplement ce qui suit à votre ~/.bash_profile. Et, la prochaine fois que vous vous connecterez (ou si vous sourcez votre .bash_profile après cette mise à jour), vous aurez une nouvelle fonction astucieuse "nième" disponible pour diriger vos fichiers.

Exécutez-le ou mettez-le dans votre ~ / .bash_profile (si vous utilisez bash) et rouvrez bash (ou exécutez source ~/.bach_profile)

# print just the nth piped in line nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }

Ensuite, pour l'utiliser, il suffit de le canaliser. Par exemple,:

$ yes line | cat -n | nth 5 5 line

JJC
la source
1

Après avoir jeté un œil à la réponse du haut et au benchmark , j'ai implémenté une minuscule fonction d'aide:

function nth {
    if (( ${#} < 1 || ${#} > 2 )); then
        echo -e "usage: $0 \e[4mline\e[0m [\e[4mfile\e[0m]"
        return 1
    fi
    if (( ${#} > 1 )); then
        sed "$1q;d" $2
    else
        sed "$1q;d"
    fi
}

Fondamentalement, vous pouvez l'utiliser de deux manières:

nth 42 myfile.txt
do_stuff | nth 42
Ulysse BN
la source
0

J'ai mis certaines des réponses ci-dessus dans un court script bash que vous pouvez mettre dans un fichier appelé get.shet lier /usr/local/bin/get(ou tout autre nom que vous préférez).

#!/bin/bash
if [ "${1}" == "" ]; then
    echo "error: blank line number";
    exit 1
fi
re='^[0-9]+$'
if ! [[ $1 =~ $re ]] ; then
    echo "error: line number arg not a number";
    exit 1
fi
if [ "${2}" == "" ]; then
    echo "error: blank file name";
    exit 1
fi
sed "${1}q;d" $2;
exit 0

Assurez-vous qu'il est exécutable avec

$ chmod +x get

Lien pour le rendre disponible sur le PATHavec

$ ln -s get.sh /usr/local/bin/get

Profitez de façon responsable!

P

polariser
la source