web-dev-qa-db-fra.com

Qu'est-ce qui est plus rapide pour supprimer la première ligne du fichier ... sed ou tail?

Dans cette réponse ( Comment puis-je supprimer la première ligne d'un fichier avec sed? ) il existe deux méthodes pour supprimer le premier enregistrement d'un fichier:

sed '1d' $file >> headerless.txt

** ---------------- OR ---------------- **

tail -n +2 $file >> headerless.txt

Personnellement, je pense que l’option tail est plus esthétique et plus lisible d’un point de vue esthétique, mais c’est probablement parce que j’ai été mis au défi.

Quelle méthode est la plus rapide?

14
WinEunuuchs2Unix

Performances de sedpar rapport à tailpour supprimer la première ligne d'un fichier

TL; DR

  • sedest très puissant et polyvalent, mais c’est ce qui le ralentit, en particulier pour les fichiers volumineux comportant de nombreuses lignes.

  • tailne fait qu'une chose simple, mais elle le fait bien et rapidement, même pour des fichiers plus volumineux comportant de nombreuses lignes.

Pour les fichiers de taille petite et moyenne, sedet tailse comportent de manière similaire (ou lente, en fonction de vos attentes). Toutefois, pour les fichiers d'entrée plus volumineux (plusieurs Mo), la différence de performances augmente considérablement (un ordre de grandeur pour les fichiers de l'ordre de centaines de Mo), avec tailnettement plus performant que sedname__.

Expérience

Préparations générales:

Nos commandes à analyser sont:

sed '1d' testfile > /dev/null
tail -n +2 testfile > /dev/null

Notez que je redirige la sortie vers /dev/null à chaque fois pour éliminer la sortie du terminal ou le fichier écrit en tant que goulot d'étranglement des performances.

Configurons un disque RAM pour éliminer les E/S du disque en tant que goulot d'étranglement potentiel. J'ai personnellement un tmpfsmonté à /tmp et j'ai donc simplement placé mon testfilepour cette expérience.

Ensuite, une fois, je crée un fichier de test aléatoire contenant un nombre spécifié de lignes $numoflines avec une longueur de ligne aléatoire et des données aléatoires à l'aide de cette commande (notez que ce n'est certainement pas optimal, il devient vraiment lent pour environ 2 millions de lignes, mais peu importe. la chose que nous analysons):

cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n "$numoflines" > testfile

Oh, au fait. mon ordinateur portable de test exécute Ubuntu 16.04, 64 bits sur un processeur Intel i5-6200U. Juste pour comparaison.

Timing gros fichiers:

Configuration d’un énorme testfilename__:

L'exécution de la commande ci-dessus avec numoflines=10000000 a généré un fichier aléatoire contenant 10 millions de lignes, occupant un peu plus de 600 Mo. C'est assez énorme, mais commençons par cela, car nous pouvons:

$ wc -l testfile 
10000000 testfile

$ du -h testfile 
611M    testfile

$ head -n 3 testfile 
qOWrzWppWJxx0e59o2uuvkrfjQbzos8Z0RWcCQPMGFPueRKqoy1mpgjHcSgtsRXLrZ8S4CU8w6O6pxkKa3JbJD7QNyiHb4o95TSKkdTBYs8uUOCRKPu6BbvG
NklpTCRzUgZK
O/lcQwmJXl1CGr5vQAbpM7TRNkx6XusYrO

Effectuez la course chronométrée avec notre énorme testfilename__:

Faisons maintenant un seul cycle chronométré avec les deux commandes en premier pour estimer avec quelle ampleur nous travaillons.

$ time sed '1d' testfile > /dev/null
real    0m2.104s
user    0m1.944s
sys     0m0.156s

$ time tail -n +2 testfile > /dev/null
real    0m0.181s
user    0m0.044s
sys     0m0.132s

Nous voyons déjà un résultat vraiment clair pour les gros fichiers, tailest une magnitude plus rapide que sedname__. Mais juste pour le plaisir et pour être sûr qu'il n'y a pas d'effets secondaires aléatoires qui font une grande différence, faisons-le 100 fois:

$ time for i in {1..100}; do sed '1d' testfile > /dev/null; done
real    3m36.756s
user    3m19.756s
sys     0m15.792s

$ time for i in {1..100}; do tail -n +2 testfile > /dev/null; done
real    0m14.573s
user    0m1.876s
sys     0m12.420s

La conclusion reste la même, sedest inefficace pour supprimer la première ligne d'un fichier volumineux, taildevrait y être utilisé.

Et oui, je sais que les constructions de boucle de Bash sont lentes, mais nous ne faisons que relativement peu d'itérations ici et le temps pris par une boucle en clair n'est pas significatif par rapport à l'exécution sedname __/tailde toute façon.

Timing petits fichiers:

Configuration d'un petit testfilename__:

Pour être complet, examinons le cas le plus courant où vous avez un petit fichier d’entrée dans la plage de Ko. Créons un fichier d'entrée aléatoire avec numoflines=100, ressemblant à ceci:

$ wc -l testfile 
100 testfile

$ du -h testfile 
8,0K    testfile

$ head -n 3 testfile 
tYMWxhi7GqV0DjWd
pemd0y3NgfBK4G4ho/
aItY/8crld2tZvsU5ly

Effectuez la course chronométrée avec notre petit testfilename__:

Comme nous pouvons nous attendre à ce que le minutage de ces petits fichiers soit de l'ordre de quelques millisecondes, faisons tout de suite 1000 itérations:

$ time for i in {1..1000}; do sed '1d' testfile > /dev/null; done
real    0m7.811s
user    0m0.412s
sys     0m7.020s

$ time for i in {1..1000}; do tail -n +2 testfile > /dev/null; done
real    0m7.485s
user    0m0.292s
sys     0m6.020s

Comme vous pouvez le constater, les horaires sont assez similaires, il n’ya pas grand chose à interpréter ou à poser. Pour les petits fichiers, les deux outils conviennent également.

28
Byte Commander

Voici une autre alternative, utilisant seulement les commandes intégrées bash et cat:

{ read ; cat > headerless.txt; } < $file

$file est redirigé vers le groupe de commandes { }. read lit et supprime simplement la première ligne. Le reste du flux est ensuite redirigé vers cat qui l’écrit dans le fichier de destination.

Sur mon Ubuntu 16.04, les performances de ceci et de la solution tail sont très similaires. J'ai créé un fichier de test long avec seq:

$ seq 100000000 > 100M.txt
$ ls -l 100M.txt 
-rw-rw-r-- 1 ubuntu ubuntu 888888898 Dec 20 17:04 100M.txt
$

tail solution:

$ time tail -n +2 100M.txt > headerless.txt

real    0m1.469s
user    0m0.052s
sys 0m0.784s
$ 

cat/brace solution:

$ time { read ; cat > headerless.txt; } < 100M.txt 

real    0m1.877s
user    0m0.000s
sys 0m0.736s
$ 

Je n'ai qu'un Ubuntu VM à portée de main pour l'instant, et j'ai constaté des variations importantes dans le minutage des deux, bien qu'ils se situent tous dans le même ordre de grandeur.

5
Digital Trauma

En essayant sur mon système, et en préfixant chaque commande avec time, j'ai obtenu les résultats suivants:

sed:

real    0m0.129s
user    0m0.012s
sys     0m0.000s

et la queue:

real    0m0.003s
user    0m0.000s
sys     0m0.000s

ce qui suggère que, sur mon système au moins AMD FX 8250 sous Ubuntu 16.04, la queue est nettement plus rapide. Le fichier de test comportait 10 000 lignes d’une taille de 540k. Le fichier a été lu à partir d'un disque dur.

4
Nick Sillito

Il n'y a pas de moyen objectif de dire lequel est le meilleur, parce que sed et tail ne sont pas les seules choses qui s'exécutent sur un système pendant l'exécution du programme. De nombreux facteurs tels que les entrées/sorties sur disque, les entrées/sorties sur réseau, les interruptions de la CPU pour les processus de priorité plus élevée - tous ces facteurs influencent la vitesse d'exécution de votre programme.

Les deux d'entre eux sont écrits en C, il ne s'agit donc pas d'un problème de langage, mais de problème environnemental. Par exemple, j’ai un disque dur SSD et, sur mon système, cela prendra du temps en microsecondes, mais pour un même fichier sur un disque dur, cela prendra plus de temps car les disques durs sont beaucoup plus lents. Le matériel joue donc également un rôle à cet égard.

Il y a quelques points que vous voudrez peut-être garder à l'esprit lorsque vous choisirez la commande à choisir:

  • Quel est ton but ? sed est un éditeur de flux pour la transformation de texte. tail est destiné à la sortie de lignes de texte spécifiques. Si vous souhaitez traiter des lignes et les imprimer uniquement, utilisez tail. Si vous souhaitez modifier le texte, utilisez sed.
  • tail a une syntaxe beaucoup plus simple que sed, utilisez donc ce que vous pouvez lire vous-même et ce que les autres peuvent lire.

Un autre facteur important est la quantité de données que vous traitez. Les petits fichiers ne vous donneront aucune différence de performance. L'image devient intéressante lorsque vous traitez avec de gros fichiers. Avec un fichier BIGFILE.txt de 2 Go, nous pouvons constater que sed a beaucoup plus d'appels système que tail et s'exécute beaucoup plus lentement.

bash-4.3$ du -sh BIGFILE.txt 
2.0G    BIGFILE.txt
bash-4.3$ strace -c  sed '1d' ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 59.38    0.079781           0    517051           read
 40.62    0.054570           0    517042           write
  0.00    0.000000           0        10         1 open
  0.00    0.000000           0        11           close
  0.00    0.000000           0        10           fstat
  0.00    0.000000           0        19           mmap
  0.00    0.000000           0        12           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         7         7 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         2         2 statfs
  0.00    0.000000           0         1           Arch_prctl
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.134351               1034177        11 total
bash-4.3$ strace -c  tail  -n +2 ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 62.30    0.148821           0    517042           write
 37.70    0.090044           0    258525           read
  0.00    0.000000           0         9         3 open
  0.00    0.000000           0         8           close
  0.00    0.000000           0         7           fstat
  0.00    0.000000           0        10           mmap
  0.00    0.000000           0         4           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         3         3 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           Arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.238865                775615         7 total
1
Sergiy Kolodyazhnyy

La première réponse ne tenait pas compte du disque en faisant > /dev/null

si vous avez un fichier volumineux et que vous ne voulez pas créer de copie temporaire sur votre disque, essayez vim -c

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time sed -i '1d' testfile

real    0m59.053s
user    0m9.625s
sys     0m48.952s

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time vim -e -s testfile -c ':1d' -c ':wq'

real    0m8.259s
user    0m3.640s
sys     0m3.093s

Edit: si le fichier est plus volumineux que la mémoire disponible, vim -c ne fonctionne pas, semble ne pas être assez intelligent pour effectuer un chargement incrémentiel du fichier.

1
StevenWernerCS

D'autres réponses montrent bien ce qui est préférable pour créer un nouveau fichier avec la première ligne manquante. Si vous souhaitez modifier un fichier plutôt que de créer un nouveau fichier, je parie que edserait plus rapide car il ne devrait pas créer de nouveau fichier du tout. Mais vous devez chercher comment supprimer une ligne avec edcar je ne l'ai utilisée qu'une fois.

0
akostadinov