cat um número muito grande de arquivos juntos na ordem correta

19

Tenho cerca de 15.000 arquivos com o nome file_1.pdb , file_2.pdb etc. Posso usar alguns milhares desses para fazer:

cat file_{1..2000}.pdb >> file_all.pdb

No entanto, se eu fizer isso por 15.000 arquivos, recebo o erro

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

Eu vi esse problema sendo resolvido fazendo find . -name xx -exec xx , mas isso não preservaria a ordem com a qual os arquivos são unidos. Como posso conseguir isso?

    
por sodiumnitrate 26.02.2018 / 18:25

6 respostas

47

Usando find , sort e xargs :

find . -maxdepth 1 -type f -name 'file_*.pdb' -print0 |
sort -zV |
xargs -0 cat >all.pdb

O comando find localiza todos os arquivos relevantes e, em seguida, imprime seus nomes de caminho para sort que faz uma "classificação de versão" para colocá-los na ordem correta (se os números dos nomes de arquivos foram preenchidos com zero) largura fixa não precisaríamos de -V ). xargs pega essa lista de nomes de caminhos ordenados e executa cat nestes grupos em lotes tão grandes quanto possível.

Isso deve funcionar mesmo se os nomes de arquivos contiverem caracteres estranhos, como novas linhas e espaços. Usamos -print0 com find para fornecer nomes com sort nul-terminados para classificar e sort os manipula usando -z . xargs também lê nomes nul-terminados com seu -0 flag.

Observe que estou escrevendo o resultado em um arquivo cujo nome não corresponde ao padrão file_*.pdb .

A solução acima usa alguns sinalizadores não padrão para alguns utilitários. Eles são suportados pela implementação GNU desses utilitários e pelo menos pelo OpenBSD e pela implementação do macOS.

Os sinalizadores não padrão usados são

  • -maxdepth 1 , para fazer com que find entre apenas no diretório mais superior, mas sem subdiretórios. POSIXly, use find . ! -name . -prune ...
  • -print0 , para tornar find resultados de nomes terminados em nul (isto foi considerado por POSIX mas rejeitado). Pode-se usar -exec printf '%s-z' {} + .
  • sort , para fazer com que -V leve registros nul-terminados. Não há equivalência POSIX.
  • sort , para tornar 200 sort, por ex. 3 após -0 . Não há equivalência POSIX, mas pode ser substituída por uma classificação numérica em partes específicas do nome do arquivo, se os nomes dos arquivos tiverem um prefixo fixo.
  • xargs , para fazer xargs ler registros terminados em nulo. Não há equivalência POSIX. POSIXly, seria necessário citar os nomes dos arquivos em um formato reconhecido por -V .

Se os nomes dos caminhos são bem comportados, e se a estrutura de diretório é plana (sem subdiretórios), então pode-se fazer sem esses sinalizadores, exceto por sort with %code% .

    
por 26.02.2018 / 18:33
14

Com zsh (de onde vem esse operador {1..15000} ):

autoload zargs # best in ~/.zshrc
zargs file_{1..15000}.pdb -- cat > file_all.pdb

Ou para todos os arquivos file_<digits>.pdb em ordem numérica:

zargs file_<->.pdb(n) -- cat > file_all.pdb

(onde <x-y> é um operador glob que corresponde aos números decimais x a y. Sem x nem y , é qualquer número decimal. Equivalente a extendedglob [0-9]## ou kshglob +([0-9]) (um ou mais dígitos)).

Com ksh93 , usando seu comando cat integrado (portanto, não é afetado por esse limite da chamada do sistema execve() , pois não há execução ):

command /opt/ast/bin/cat file_{1..15000}.pdb > file_all.pdb

Com bash / zsh / ksh93 (que suportam zsh {x..y} e têm printf incorporado):

printf '%s\n' file_{1..15000}.pdb | xargs cat > file_all.pdb

Em um sistema GNU ou compatível, você também pode usar seq :

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

Para as soluções baseadas em xargs , deve-se ter cuidado especial com nomes de arquivos que contenham espaços em branco, aspas simples ou duplas ou barras invertidas.

Como para -It's a trickier filename - 12.pdb , use:

seq -f "\"./-It's a trickier filename - %.17g.pdb\"" 15000 |
  xargs cat > file_all.pdb
    
por 26.02.2018 / 18:52
10

Um loop for é possível e muito simples.

for i in file_{1..15000}.pdb; do cat $i >> file_all.pdb; done

A desvantagem é que você invoca cat um monte de vezes. Mas se você não consegue lembrar exatamente como fazer as coisas com find e a sobrecarga de invocação não é tão ruim em sua situação, então vale a pena ter em mente.

    
por 26.02.2018 / 19:54
3
seq 1 15000 | awk '{print "file_"$0".dat"}' | xargs cat > file_all.pdb
    
por 26.02.2018 / 21:12
2

Premissa

Você não deve incorrer nesse erro apenas para arquivos de 15k com o formato de nome específico [ 1 , 2 ] .

Se você estiver executando essa expansão a partir de outro diretório e precisar adicionar o caminho para cada arquivo, o tamanho do comando será maior e, é claro, isso poderá ocorrer.

Solução executa o comando desse diretório.

(cd That/Directory ; cat file_{1..2000}.pdb >> file_all.pdb )

Melhor solução Se em vez disso eu acho ruim e você o executa do diretório no qual os arquivos estão ...
IMHO a melhor solução é os os de Stéphane Chazelas :

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

com printf ou seq; testado em arquivos de 15k com apenas o seu número dentro do pré-cache, é até o mais rápido (atualmente e exceto o OP do mesmo diretório no qual os arquivos estão).

Algumas palavras mais

Você deve conseguir passar para as linhas de comando do seu shell por mais tempo.
Sua linha de comando tem 213914 caracteres e contém 15003 palavras
cat file_{1..15000}.pdb " > file_all.pdb" | wc

... até mesmo adicionando 8 bytes para cada palavra é 333 938 bytes (0.3M) bem abaixo do 2097142 (2.1M) relatado por ARG_MAX em um kernel 3.13.0 ou o menor 2088232 reportado como "Comprimento máximo de comando que podemos usar" por xargs --show-limits

Dê uma olhada no seu sistema para a saída de

getconf ARG_MAX
xargs --show-limits

Solução guiada por preguiça

Em casos como esse, prefiro trabalhar com blocos, mesmo que normalmente exista uma solução eficiente em termos de tempo.
A lógica (se houver) é que eu sou muito preguiçoso para escrever 1 ... 1000 1001..2000 etc etc ...
Então eu peço um script para fazer isso por mim.
Somente depois que eu verifiquei que a saída está correta, eu redireciono para um script.

... mas preguiça é um estado de espírito .
Desde que eu sou alérgico a xargs (eu realmente deveria ter usado xargs aqui) e eu não quero verificar como usá-lo, eu termino pontualmente para reinventar a roda como nos exemplos abaixo (tl; dr).

Observe que, como os nomes dos arquivos são controlados (sem espaços, novas linhas ...), você pode ir facilmente com algo parecido com o script abaixo.

tl; dr

Versão 1: passe como parâmetro opcional o primeiro número de arquivo, o último, o tamanho do bloco, o arquivo de saída

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;  
    cat $(seq -f file_%.17g.pdb $CurrentStart $CurrentEnd)  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    cat $(seq -f file_%.17g.pdb $CurrentStart $EndN)  >> $OutFile;

Versão 2

Chamando bash para a expansão (um pouco mais lento nos meus testes ~ 20%).

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;
    echo  cat file_{$CurrentStart..$CurrentEnd}.pdb | /bin/bash  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    echo  cat file_{$CurrentStart..$EndN}.pdb | /bin/bash  >> $OutFile;

É claro que você pode seguir em frente e se livrar completamente de seq [ 3 ] (do coreutils) e trabalhe diretamente com as variáveis no bash, ou use python, ou compile o programa ac para fazer isso [ 4 ] ...

    
por 27.02.2018 / 12:08
0

Outra maneira de fazer isso pode ser

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
    
por 27.02.2018 / 15:51