J'ai un problème étrange avec les gros fichiers et bash. Voici le contexte:
- J'ai un gros fichier: 75G et 400 000 000+ lignes (c'est un fichier journal, mon mauvais, je l'ai laissé grandir).
- Les 10 premiers caractères de chaque ligne sont des horodatages au format AAAA-MM-JJ.
- Je veux diviser ce fichier: un fichier par jour.
J'ai essayé avec le script suivant qui n'a pas fonctionné. Ma question concerne ce script qui ne fonctionne pas, pas les solutions alternatives .
while read line; do
new_file=${line:0:10}_file.log
echo "$line" >> $new_file
done < file.log
Après le débogage, j'ai trouvé le problème dans la new_filevariable. Ce script:
while read line; do
new_file=${line:0:10}_file.log
echo $new_file
done < file.log | uniq -c
donne le résultat ci-dessous (je mets les xes pour garder les données confidentielles, les autres caractères sont les vrais). Remarquez la dhet les chaînes plus courtes:
...
27402 2011-xx-x4
27262 2011-xx-x5
22514 2011-xx-x6
17908 2011-xx-x7
...
3227382 2011-xx-x9
4474604 2011-xx-x0
1557680 2011-xx-x1
1 2011-xx-x2
3 2011-xx-x1
...
12 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
1 208--
1 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
...
Ce n'est pas un problème dans le format de mon fichier . Le script cut -c 1-10 file.log | uniq -cne donne que des horodatages valides. Fait intéressant, une partie de la sortie ci-dessus devient avec cut ... | uniq -c:
3227382 2011-xx-x9
4474604 2011-xx-x0
5722027 2011-xx-x1
Nous pouvons voir qu'après le décompte uniq 4474604, mon script initial a échoué.
Ai-je atteint une limite en bash que je ne connais pas, ai-je trouvé un bug dans bash (cela semble improbable), ou ai-je fait quelque chose de mal?
Mise à jour :
Le problème se produit après la lecture de 2G du fichier. Les coutures readet la redirection n'aiment pas les fichiers plus gros que 2G. Mais toujours à la recherche d'une explication plus précise.
Update2 :
Cela ressemble définitivement à un bug. Il peut être reproduit avec:
yes "0123456789abcdefghijklmnopqrs" | head -n 100000000 > file
while read line; do file=${line:0:10}; echo $file; done < file | uniq -c
mais cela fonctionne bien comme solution de contournement (il semble que j'ai trouvé une utilisation utile de cat):
cat file | while read line; do file=${line:0:10}; echo $file; done | uniq -c
Un bogue a été déposé sur GNU et Debian. Les versions concernées sont bash4.1.5 sur Debian Squeeze 6.0.2 et 6.0.4.
echo ${BASH_VERSINFO[@]}
4 1 5 1 release x86_64-pc-linux-gnu
Update3:
Grâce à Andreas Schwab qui a réagi rapidement à mon rapport de bug, c'est le patch qui est la solution à ce mauvais comportement. Le fichier impacté est lib/sh/zread.ccomme Gilles l'a souligné plus tôt:
diff --git a/lib/sh/zread.c b/lib/sh/zread.c index 0fd1199..3731a41 100644
--- a/lib/sh/zread.c
+++ b/lib/sh/zread.c @@ -161,7 +161,7 @@ zsyncfd (fd)
int fd; { off_t off;
- int r;
+ off_t r;
off = lused - lind; r = 0;
La rvariable est utilisée pour conserver la valeur de retour de lseek. Comme lseekrenvoie le décalage depuis le début du fichier, lorsqu'il est supérieur à 2 Go, la intvaleur est négative, ce qui entraîne l' if (r >= 0)échec du test là où il aurait dû réussir.
readinstruction en bash.