Unisci in modo efficiente / ordina / univoco numero elevato di file di testo


8

Sto provando un ingenuo:

$ cat * | sort -u > /tmp/bla.txt

che fallisce con:

-bash: /bin/cat: Argument list too long

Quindi, al fine di evitare una soluzione sciocca come (crea un enorme file temporaneo):

$ find . -type f -exec cat {} >> /tmp/unsorted.txt \;
$ cat /tmp/unsorted.txt | sort -u > /tmp/bla.txt

Ho pensato di poter elaborare i file uno a uno usando (questo dovrebbe ridurre il consumo di memoria ed essere più vicino a un meccanismo di streaming):

$ cat proc.sh
#!/bin/sh
old=/tmp/old.txt
tmp=/tmp/tmp.txt
cat $old "$1" | sort -u > $tmp
mv $tmp $old

Seguito quindi da:

$ touch /tmp/old.txt
$ find . -type f -exec /tmp/proc.sh {} \;

Esiste una sostituzione più semplice in stile unix per: cat * | sort -uquando il numero di file raggiunge MAX_ARG? È strano scrivere una piccola shell script per un compito così comune.


2
è necessaria la concatenazione? sortlo fa automaticamente per l'immissione di più file .. ma poi sort -u *fallirei anche con Argument list too longsuppongo
Sundeep

Risposte:


8

Con GNU sorte una shell in cui printfè incorporato (tutti quelli simili a POSIX al giorno d'oggi tranne alcune varianti di pdksh):

printf '%s\0' * | sort -u --files0-from=- > output

Ora, un problema è che, poiché i due componenti di quella pipeline vengono eseguiti contemporaneamente e indipendentemente, quando quello sinistro espande il *glob, quello destro potrebbe aver outputgià creato il file che potrebbe causare problemi (forse non con -uqui) come outputsarebbe sia un file di input che di output, quindi potresti voler fare in modo che l'output vada in un'altra directory ( > ../outputper esempio), o assicurati che il glob non corrisponda al file di output.

Un altro modo per affrontarlo in questo caso è scriverlo:

printf '%s\0' * | sort -u --files0-from=- -o output

In questo modo, si sta sortaprendo outputper la scrittura e (nei miei test), non lo farà prima di aver ricevuto l'elenco completo dei file (molto tempo dopo l'espansione del glob). Eviterà anche di bloccare outputse nessuno dei file di input è leggibile.

Un altro modo di scriverlo con zshobash

sort -u --files0-from=<(printf '%s\0' *) -o output

Sta usando la sostituzione del processo (dove <(...)viene sostituito da un percorso di file che si riferisce all'estremità di lettura della pipe su cui printfsta scrivendo). Questa funzione viene ksh, ma kshinsiste nel fare l'espansione di <(...)un argomento separato al comando in modo da non poterlo usare con la --option=<(...)sintassi. Funzionerebbe con questa sintassi però:

sort -u --files0-from <(printf '%s\0' *) -o output

Nota che vedrai una differenza dagli approcci che alimentano l'output dei catfile nei casi in cui ci sono file che non finiscono con un carattere di nuova riga:

$ printf a > a
$ printf b > b
$ printf '%s\0' a b | sort -u --files0-from=-
a
b
$ printf '%s\0' a b | xargs -r0 cat | sort -u
ab

Si noti inoltre che sortordina utilizzando l'algoritmo di confronto nelle impostazioni locali ( strcollate()) e sort -uriporta una di ciascuna serie di righe che ordinano lo stesso in base all'algoritmo, non linee univoche a livello di byte. Se ti interessa solo che le linee siano univoche a livello di byte e non ti interessi così tanto all'ordine in cui sono ordinate, potresti voler correggere le impostazioni locali su C dove l'ordinamento si basa su valori di byte ( memcmp(); questo probabilmente accelererebbe le cose in modo significativo):

printf '%s\0' * | LC_ALL=C sort -u --files0-from=- -o output

Sembra più naturale scrivere, questo dà anche l'opportunità sortdi ottimizzare il suo consumo di memoria. printf '%s\0' *Tuttavia, trovo ancora un po 'complesso da scrivere.
Malat,

È possibile utilizzare find . -type f -maxdepth 1 -print0invece di printf '%s\0' *, ma non posso affermare che sia più facile da digitare. E quest'ultimo è più facile da definire come alias, ovviamente!
Toby Speight,

@TobySpeight echoha un -n, avrei preferito qualcosa di simile a printf -0 %sun livello un po 'meno basso di'%s\0'
malat

@Toby -maxdepthe -print0sono estensioni GNU (anche se ampiamente supportate in questi giorni). Con altri finds (anche se se si dispone di un ordinamento GNU, è probabile che anche GNU lo trovi), è possibile farlo LC_ALL=C find . ! -name . -prune -type f ! -name '.*' -exec printf '%s\0' {} +( LC_ALL=Cper escludere ancora i file nascosti che contengono caratteri non validi, anche con GNU find), ma è un po 'eccessivo quando in genere ho printfincorporato.
Stéphane Chazelas,

2
@malat, potresti sempre definire una print0funzione come print0() { [ "$#" -eq 0 ] || printf '%s\0' "$@";}e poiprint0 * | sort...
Stéphane Chazelas,

11

Una semplice correzione, funziona almeno in Bash, poiché printfè integrata e i limiti dell'argomento della riga di comando non si applicano ad esso:

printf "%s\0" * | xargs -0 cat | sort -u > /tmp/bla.txt

( echo * | xargsfunzionerebbe anche, tranne per la gestione dei nomi dei file con spazi bianchi ecc.)


Questa sembra una risposta migliore di quella accettata, poiché non richiede la generazione di un catprocesso separato per ogni file.
LarsH,

4
@LarsH, raggruppa find -exec {} +più file per un'esecuzione. Con find -exec \;esso sarebbe un gatto per file.
ilkkachu,

Ah, buono a sapersi. (Imbottitura)
LarsH,

9
find . -maxdepth 1 -type f ! -name ".*" -exec cat {} + | sort -u -o /path/to/sorted.txt

Ciò concatenerà tutti i file regolari non nascosti nella directory corrente e ordinerà i loro contenuti combinati (rimuovendo le righe duplicate) nel file /path/to/sorted.txt.


Stavo cercando di utilizzare solo due file alla volta per evitare di consumare molta memoria (il mio numero di file è piuttosto grande). Credi che |eseguiranno correttamente le operazioni a catena per limitare l'utilizzo della memoria?
Malat,

2
@malat sorteseguirà un ordinamento out-of-core se i requisiti di memoria lo richiedono. La parte sinistra della pipeline consumerà pochissima memoria in confronto.
Kusalananda

1

Efficienza è un termine relativo, quindi devi davvero specificare quale fattore vuoi minimizzare; CPU, memoria, disco, tempo ecc. Per ragioni di argomento, suppongo che tu abbia voluto ridurre al minimo l'utilizzo della memoria e sei disposto a spendere più cicli di CPU per raggiungere questo obiettivo. Soluzioni come quella fornita da Stéphane Chazelas funzionano bene

sort -u --files0-from <(printf '%s\0' *) > ../output

ma presumono che i singoli file di testo abbiano inizialmente un alto grado di unicità. Se non lo fanno, cioè se dopo

sort -u < sample.txt > sample.srt

sample.srt è più piccolo del 10% rispetto a sample.txt quindi risparmierai memoria significativa rimuovendo i duplicati all'interno dei file prima di unirli. Inoltre risparmierai ancora più memoria non concatenando i comandi, il che significa che i risultati di diversi processi non devono necessariamente essere in memoria contemporaneamente.

find /somedir -maxdepth 1 type f -exec sort -u -o {} {} \;
sort -u --files0-from <(printf '%s\0' *) > ../output

1
L'uso della memoria è raramente un problema in sortquanto sortricorre all'uso di file temporanei quando l'utilizzo della memoria supera una soglia (di solito relativamente piccola). base64 /dev/urandom | sort -uriempirà il disco ma non utilizzerà molta memoria.
Stéphane Chazelas,

Beh, almeno è il caso della maggior parte delle sortimplementazioni tra cui quella originale in Unix v3 nel 1972, ma a quanto pare non lo è busybox sort. Presumibilmente perché quello è destinato a funzionare su piccoli sistemi che non dispongono di memoria permanente.
Stéphane Chazelas,

Si noti che yes | sort -u(tutti i dati duplicati) non devono utilizzare più di pochi byte di memoria e tanto meno disco. Ma sortalmeno con GNU e Solaris lo vediamo scrivere molti file di 2 byte di grandi dimensioni /tmp( y\nper ogni pochi megabyte di input), quindi alla fine riempirà il disco.
Stéphane Chazelas,

0

Come @ilkkachu, ma il gatto (1) non è necessario:

printf "%s\0" * | xargs -0 sort -u

Inoltre, se i dati sono così lunghi, forse ti piacerebbe usare l'opzione sort (1) --parallel = N

Quando N è il numero di CPU del tuo computer

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.