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?
sed
par rapport à tail
pour supprimer la première ligne d'un fichiersed
est très puissant et polyvalent, mais c’est ce qui le ralentit, en particulier pour les fichiers volumineux comportant de nombreuses lignes.
tail
ne 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, sed
et tail
se 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 tail
nettement plus performant que sed
name__.
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 tmpfs
monté à /tmp
et j'ai donc simplement placé mon testfile
pour 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.
testfile
name__: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
testfile
name__: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, tail
est une magnitude plus rapide que sed
name__. 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, sed
est inefficace pour supprimer la première ligne d'un fichier volumineux, tail
devrait 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 sed
name __/tail
de toute façon.
testfile
name__: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
testfile
name__: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.
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.
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.
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:
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
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.
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 ed
serait plus rapide car il ne devrait pas créer de nouveau fichier du tout. Mais vous devez chercher comment supprimer une ligne avec ed
car je ne l'ai utilisée qu'une fois.