Ho uno strano problema con file di grandi dimensioni e bash. Questo è il contesto:
- Ho un file di grandi dimensioni: 75 G e oltre 400.000.000 di righe (è un file di registro, mio male, l'ho lasciato crescere).
- I primi 10 caratteri di ogni riga sono i timestamp nel formato AAAA-MM-GG.
- Voglio dividere quel file: un file al giorno.
Ho provato con il seguente script che non ha funzionato. La mia domanda è che questo script non funziona, non soluzioni alternative .
while read line; do
new_file=${line:0:10}_file.log
echo "$line" >> $new_file
done < file.log
Dopo il debug, ho riscontrato il problema nella new_filevariabile. Questo script:
while read line; do
new_file=${line:0:10}_file.log
echo $new_file
done < file.log | uniq -c
dà il risultato qui sotto (ho messo gli xes per mantenere i dati riservati, altri caratteri sono quelli reali). Nota le dhstringhe più brevi e:
...
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
...
Non è un problema nel formato del mio file . Lo script cut -c 1-10 file.log | uniq -cfornisce solo timestamp validi. È interessante notare che una parte dell'output sopra diventa con cut ... | uniq -c:
3227382 2011-xx-x9
4474604 2011-xx-x0
5722027 2011-xx-x1
Possiamo vedere che dopo il conteggio uniq 4474604, il mio script iniziale non è riuscito.
Ho colpito un limite in bash che non conosco, ho trovato un bug in bash (sembra improbabile) o ho fatto qualcosa di sbagliato?
Aggiornamento :
Il problema si verifica dopo aver letto 2G del file. Cuciture reade reindirizzamenti non amano file più grandi di 2G. Ma sto ancora cercando una spiegazione più precisa.
Aggiornamento2 :
Sembra definitivamente un bug. Può essere riprodotto con:
yes "0123456789abcdefghijklmnopqrs" | head -n 100000000 > file
while read line; do file=${line:0:10}; echo $file; done < file | uniq -c
ma questo funziona bene come soluzione alternativa (sembra che ho trovato utile cat):
cat file | while read line; do file=${line:0:10}; echo $file; done | uniq -c
È stato archiviato un bug su GNU e Debian. Le versioni interessate sono bash4.1.5 su Debian Squeeze 6.0.2 e 6.0.4.
echo ${BASH_VERSINFO[@]}
4 1 5 1 release x86_64-pc-linux-gnu
Update3:
Grazie ad Andreas Schwab che ha reagito rapidamente alla mia segnalazione di bug, questa è la patch che è la soluzione a questo comportamento scorretto. Il file interessato è lib/sh/zread.ccome Gilles ha sottolineato prima:
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 rvariabile viene utilizzata per contenere il valore restituito di lseek. Poiché lseekrestituisce l'offset dall'inizio del file, quando supera i 2 GB, il intvalore è negativo, causando il if (r >= 0)fallimento del test dove avrebbe dovuto avere esito positivo.
readdell'istruzione in bash.