Existe-t-il une manière "canonique" de procéder? J'utilise head -n | tail -1
ce 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.
awk
etsed
et je suis sûr que quelqu'un peut également proposer un Perl one-liner;)head | tail
solution n'est pas optimale. D'autres solutions plus optimales ont été suggérées.head | tail
solution ne fonctionne pas, si vous interrogez une ligne qui n'existe pas dans l'entrée: elle imprimera la dernière ligne.Réponses:
head
et le tuyau avectail
sera lent pour un énorme fichier. Je suggéreraissed
ceci:Où
NUM
est le numéro de la ligne que vous souhaitez imprimer; ainsi, par exemple,sed '10q;d' file
pour imprimer la 10e ligne defile
.Explication:
NUMq
quittera immédiatement lorsque le numéro de ligne estNUM
.d
supprimera la ligne au lieu de l'imprimer; cela est inhibé sur la dernière ligne car leq
fait que le reste du script soit ignoré lors de la fermeture.Si vous avez
NUM
dans une variable, vous voudrez utiliser des guillemets doubles au lieu de simples:la source
sed -n 'NUMp'
etsed 'NUM!d'
solutions proposées ci - dessous.tail -n+NUM file | head -n1
sera 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.cat
est 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 lescat
performances restent les mêmes. Curieusement, sous OS X 10.9.3 rien de tout cela ne semble faire de différence:cat
/ noncat
, fichier mis en cache ou non. @anubhava: mon plaisir.sed 'NUMq
affichera les premiersNUM
fichiers et;d
supprimera tout sauf la dernière ligne.imprime la 2e ligne
2011e ligne
ligne 10 jusqu'à ligne 33
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
la source
<
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 commesed -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) :)head
/tail
ne résout pas lesed -n '1p;3p'
scénario - alias imprimer plus de lignes non adjacentes ...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:
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
time
intégré pour comparer chaque commande.Référence
Voyons d'abord comment la
head
tail
solution: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:
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
exit
car je n'allais pas attendre que le fichier complet s'exécute: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:
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:
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
tail
solution. Au mieux, lased
solution offre une augmentation de ~ 3% de l'efficacité.(pourcentages calculés avec la formule
% = (runtime/baseline - 1) * 100
)Ligne 50 000 000
sed
perl
head|tail
awk
cut
Ligne 500 000 000
sed
perl
head|tail
awk
cut
Ligne 3,338,559,320
sed
perl
head|tail
awk
cut
la source
Avec
awk
c'est assez rapide:Lorsque cela est vrai, le comportement par défaut
awk
est effectué:{print $0}
.Versions alternatives
Si votre fichier est volumineux, vous feriez mieux
exit
aprè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 .Si vous souhaitez donner le numéro de ligne d'une variable bash, vous pouvez utiliser:
Voyez combien de temps est économisé en utilisant
exit
, spécialement si la ligne se trouve dans la première partie du fichier:La différence est donc de 0,198s contre 1,303s, environ 6 fois plus rapide.
la source
awk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3
. Avec GNU awk, cela peut être accéléré en utilisantawk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3
.FS=RS
fait-il que le champ ne soit pas divisé?FS=RS
n'évite pas le fractionnement de champs, mais il n'analyse que les $ 0 etRS
$0
FS=RS
et 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!Selon mes tests, en termes de performances et de lisibilité ma recommandation est:
tail -n+N | head -1
N
est le numéro de ligne que vous souhaitez. Par exemple,tail -n+7 input.txt | head -1
imprime la 7e ligne du fichier.tail -n+N
imprimera tout à partir de la ligneN
ethead -1
l'arrêtera après une ligne.L'alternative
head -N | tail -1
est 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 secondeshead -N | tail -1
: 4,6 secondessed Nq;d
: 18,8 secondesLes résultats peuvent différer, mais les performances
head | tail
ettail | head
sont, en général, comparables pour les entrées plus petites, etsed
sont 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:
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:
la source
head | tail
vstail | head
? Ou cela dépend-il de la ligne imprimée (début du fichier vs fin du fichier)?head -5 | tail -1
vstail -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éetail | head
plus rapide. stackoverflow.com/a/48189289Wow, toutes les possibilités!
Essaye ça:
ou l'un d'eux en fonction de votre version d'Awk:
( Vous devrez peut-être essayer la commande
nawk
ougawk
).Existe-t-il un outil qui imprime uniquement cette ligne particulière? Pas l'un des outils standard. Cependant,
sed
c'est probablement le plus proche et le plus simple à utiliser.la source
Scripts d'une ligne utiles pour sed
la source
Cette question étant étiquetée Bash, voici la façon de faire Bash (≥4): utilisez
mapfile
avec les options-s
(skip) et-n
(count).Si vous avez besoin d'obtenir la 42ème ligne d'un fichier
file
:À ce stade, vous aurez un tableau dont
ary
les champs contiennent les lignes defile
(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: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:
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'
-t
option (trim):Vous pouvez demander à une fonction de le faire pour vous:
Pas de commandes externes, seulement des builds Bash!
la source
Vous pouvez également utiliser sed print et quitter:
la source
-n
option 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.sed
toutes lessed
réponses sont à peu près à la même vitesse. Par conséquent (pour GNUsed
), c'est la meilleuresed
réponse, car cela gagnerait du temps pour les gros fichiers et les petites valeurs de nième ligne .Vous pouvez également utiliser Perl pour cela:
la source
La solution la plus rapide pour les gros fichiers est toujours tail | head, à condition que les deux distances:
S
E
sont connus. Ensuite, nous pourrions utiliser ceci:
combien est juste le nombre de lignes requises.
Un peu plus de détails dans https://unix.stackexchange.com/a/216614/79743
la source
S
etE
, (c.-à-d. Octets, caractères ou lignes).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:
puis lisez avec
tail
, quiseek
s en fait directement au point approprié dans le fichier!par exemple pour obtenir la ligne 1000:
la source
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.
J'espère que cela t'aides!
la source
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
la source
Si vous avez plusieurs lignes délimitées par \ n (normalement une nouvelle ligne). Vous pouvez également utiliser «couper»:
Vous obtiendrez la 2ème ligne du fichier.
-f3
vous donne la 3ème ligne.la source
cat FILE | cut -f2,5 -d$'\n'
affichera les lignes 2 et 5 du FICHIER. (Mais cela ne préservera pas l'ordre.)Pour imprimer la nième ligne en utilisant sed avec une variable comme numéro de ligne:
Ici, l'indicateur «-e» sert à ajouter un script à la commande à exécuter.
la source
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
la source
Après avoir jeté un œil à la réponse du haut et au benchmark , j'ai implémenté une minuscule fonction d'aide:
Fondamentalement, vous pouvez l'utiliser de deux manières:
la source
J'ai mis certaines des réponses ci-dessus dans un court script bash que vous pouvez mettre dans un fichier appelé
get.sh
et lier/usr/local/bin/get
(ou tout autre nom que vous préférez).Assurez-vous qu'il est exécutable avec
Lien pour le rendre disponible sur le
PATH
avecProfitez de façon responsable!
P
la source