Efetivamente mesclar / classificar / único grande número de arquivos de texto

7

Estou tentando um ingênuo:

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

que falha com:

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

Então, para evitar uma solução boba, como (cria um enorme arquivo temporário):

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

Eu pensei em processar arquivos um por um usando (isso deve reduzir o consumo de memória e estar mais perto de um mecanismo de streaming):

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

Seguido por:

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

Existe uma substituição mais simples no estilo do UNIX para: cat * | sort -u quando o número de arquivos atingir MAX_ARG ? Parece estranho escrever um pequeno script de shell para uma tarefa tão comum.

    
por malat 15.05.2017 / 10:52

4 respostas

8

Com GNU sort e um shell em que printf está integrado (todos os tipos semelhantes a POSIX atualmente, exceto algumas variantes de pdksh ):

printf '%s
printf '%s
sort -u --files0-from=<(printf '%s
sort -u --files0-from <(printf '%s
$ printf a > a
$ printf b > b
$ printf '%s
printf '%s
printf '%s
printf '%s
sort -u --files0-from=<(printf '%s
sort -u --files0-from <(printf '%s
$ printf a > a
$ printf b > b
$ printf '%s
printf '%s%pre%' * | LC_ALL=C sort -u --files0-from=- -o output
' a b | sort -u --files0-from=- a b $ printf '%s%pre%' a b | xargs -r0 cat | sort -u ab
' *) -o output
' *) -o output
' * | sort -u --files0-from=- -o output
' * | sort -u --files0-from=- > output
' * | LC_ALL=C sort -u --files0-from=- -o output
' a b | sort -u --files0-from=- a b $ printf '%s%pre%' a b | xargs -r0 cat | sort -u ab
' *) -o output
' *) -o output
' * | sort -u --files0-from=- -o output
' * | sort -u --files0-from=- > output

Agora, um problema é que, como os dois componentes desse pipeline são executados simultaneamente e de forma independente, no momento em que o esquerdo expande o * glob, o direito pode ter criado o arquivo output já que poderia causar problema (talvez não com -u aqui) como output seria um arquivo de entrada e saída, então você pode querer que a saída vá para outro diretório ( > ../output por exemplo), ou certifique-se de que o glob não corresponde ao arquivo de saída.

Outra maneira de abordá-lo nessa instância é escrevê-lo:

%pre%

Dessa forma, é sort opening output para gravação e (em meus testes), não será feito antes de receber a lista completa de arquivos (assim que o glob tiver sido expandido). Ele também evitará a perda de output se nenhum dos arquivos de entrada for legível.

Outra maneira de escrever com zsh ou bash

%pre%

Isso está usando a substituição de processo (em que <(...) é substituído por um caminho de arquivo que se refere à extremidade de leitura do canal em que printf está gravando). Esse recurso vem de ksh , mas ksh insiste em tornar a expansão de <(...) um argumento separado para o comando, portanto, você não pode usá-lo com a sintaxe --option=<(...) . Funcionaria com esta sintaxe embora:

%pre%

Note que você verá uma diferença de abordagens que alimentam a saída de cat nos arquivos nos casos em que há arquivos que não terminam em um caractere de nova linha:

%pre%

Observe também que sort ordena o uso do algoritmo de intercalação na localidade ( strcollate() ) e sort -u informa um de cada conjunto de linhas que classificam o mesmo por esse algoritmo, não linhas exclusivas no nível de byte. Se você se preocupa apenas com o fato de as linhas serem exclusivas em nível de byte e não se importarem tanto com a ordem em que são classificadas, talvez você queira corrigir a localidade para C, onde a classificação é baseada em valores de bytes ( memcmp() ; provavelmente aceleraria as coisas significativamente):

%pre%     
por 15.05.2017 / 17:44
11

Uma correção simples funciona pelo menos no Bash, já que printf está embutido e os limites de argumento da linha de comando não se aplicam a ele:

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

( echo * | xargs também funcionaria, exceto pelo tratamento de nomes de arquivo com espaço em branco, etc.)

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

Isso concatenará todos os arquivos regulares não ocultos no diretório atual e classificará seu conteúdo combinado (ao remover linhas duplicadas) no arquivo /path/to/sorted.txt .

    
por 15.05.2017 / 10:56
0

A eficiência é um termo relativo, então você realmente precisa especificar qual fator deseja minimizar; CPU, memória, disco, tempo, etc. Por uma questão de argumento, vou assumir que você queria minimizar o uso de memória e estão dispostos a gastar mais ciclos de CPU para conseguir isso. Soluções como as dadas por Stéphane Chazelas funcionam bem

sort -u --files0-from <(printf '%s
sort -u < sample.txt > sample.srt
' *) > ../output

mas eles assumem que os arquivos de texto individuais têm um alto grau de exclusividade para começar. Se não, ou seja, se depois de

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

sample.srt é mais de 10% menor que sample.txt, em seguida, você salvará memória significativa removendo as duplicatas nos arquivos antes de mesclar. Você também economizará ainda mais memória não encadeando os comandos, o que significa que os resultados de diferentes processos não precisam estar na memória ao mesmo tempo.

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

Tags