reunir um número muito grande de arquivos na ordem correta

23

Eu tenho cerca de 15.000 arquivos nomeados file_1.pdb, file_2.pdbetc. Eu posso criar alguns milhares deles em ordem, fazendo o seguinte:

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

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

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

Eu já vi esse problema sendo resolvido, find . -name xx -exec xxmas isso não preservaria a ordem em que os arquivos foram adicionados. Como posso conseguir isso?

nitrato de sódio
fonte
3
Qual o nome do décimo arquivo? (Ou qualquer arquivo com mais de um dígito de ordem numerada).
roaima 26/02
Eu (agora) tenho 15.000 desses arquivos em um diretório e sua cat file_{1..15000}.pdbconstrução funciona bem para mim.
roaima 26/02
11
depende do sistema qual é o limite. getconf ARG_MAXDeveria dizer.
22418 ilkkachu
3
Considere alterar sua pergunta para "milhares de" ou "um número muito grande de" arquivos. Pode tornar a pergunta mais fácil de encontrar para outras pessoas com um problema semelhante.
msouth

Respostas:

49

Usando find, sorte xargs:

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

O findcomando localiza todos os arquivos relevantes e, em seguida, imprime seus nomes de caminho para sortque eles façam uma "classificação de versão" para obtê-los na ordem correta (se os números nos nomes de arquivos tivessem sido preenchidos com zero a uma largura fixa, não precisaríamos -V). xargspega essa lista de nomes de caminhos classificados e os executa catem lotes tão grandes quanto possível.

Isso deve funcionar mesmo que os nomes de arquivos contenham caracteres estranhos, como novas linhas e espaços. Nós usamos -print0com findpara dar sortnomes com terminação nula para classificar e sortmanipula-os usando -z. xargstambém lê nomes terminados em nulo com seu -0sinalizador.

Observe que estou gravando 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, pela implementação do OpenBSD e do macOS.

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

  • -maxdepth 1, para findinserir apenas o diretório mais alto, mas sem subdiretórios. POSIXly, usefind . ! -name . -prune ...
  • -print0, para criar findnomes de caminho com terminação nula (isso foi considerado pelo POSIX, mas rejeitado). Pode-se usar em seu -exec printf '%s\0' {} +lugar.
  • -z, para criar sortregistros com terminação nula. Não há equivalência POSIX.
  • -V, para sortordenar, por exemplo, 200depois 3. 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 de arquivos tiverem um prefixo fixo.
  • -0, para fazer xargsregistros com terminação nula. Não há equivalência POSIX. POSIX, seria necessário citar os nomes dos arquivos em um formato reconhecido por xargs.

Se os nomes de caminho forem bem comportados, e se a estrutura de diretórios for plana (sem subdiretórios), seria possível se contentar com esses sinalizadores, exceto -Vcom sort.

Kusalananda
fonte
11
Você não precisa de terminação nula fora do padrão para isso. Esses nomes de arquivos são extremamente chatos e as ferramentas POSIX são totalmente capazes de lidar com isso.
27718 Kevin
6
Você também pode escrever isso de forma mais sucinta com a especificação do autor printf ‘file_%d.pdb\0’ {1..15000} | xargs -0 cat, ou mesmo com o argumento de Kevin echo file_{1..15000}.pdb | xargs cat. A findsolução tem uma sobrecarga consideravelmente maior, pois precisa procurar esses arquivos no sistema de arquivos, mas é mais útil quando alguns dos arquivos podem não existir.
Kojiro
4
@ Kevin enquanto o que você está dizendo é verdade, é sem dúvida melhor ter uma resposta que se aplica em circunstâncias mais gerais. Das próximas mil pessoas que têm essa pergunta, é provável que algumas delas tenham espaços ou qualquer outra coisa em seus nomes de arquivo.
27718 ms ms
11
@chrylis Um redirecionamento nunca faz parte dos argumentos de um comando e, em xargsvez catdisso, é redirecionado (cada catchamada usará a xargssaída padrão). Se tivéssemos dito xargs -0 sh -c 'cat >all.pdb', teria feito sentido usar em >>vez de >, se é isso que você está sugerindo.
Kusalananda
11
Parece sort -n -k1.6que funcionaria (para o original, file_nnnnomes de arquivos ou sort -n -k1.5para aqueles sem o sublinhado).
Scott
14

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

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

Ou para todos os file_<digits>.pdbarquivos 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 xnem y, é qualquer número decimal. Equivalente a extendedglob's ' [0-9]##ou kshglob's +([0-9])(um ou mais dígitos)).

Com ksh93, usando seu catcomando embutido (para não ser afetado por esse limite de execve()chamada do sistema, pois não há execução ):

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

Com bash/ zsh/ ksh93(que suporte zsh's {x..y}e têm printfembutido):

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 xargssoluções baseadas, seria necessário um cuidado especial com os nomes de arquivos que contêm 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
Stéphane Chazelas
fonte
A seq -f | xarg cat > é a solução mais elegante e eficaz. (NA MINHA HUMILDE OPINIÃO).
Hastur 27/02
Verifique o nome do arquivo mais complicado ... talvez '"./-It'\''s a trickier filename - %.17g.pdb"'?
Hastur 27/02
@Hastur, oops! Sim, obrigado, eu mudei para uma sintaxe de citação alternativa. O seu também funcionaria.
Stéphane Chazelas 27/02
11

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 catmuitas vezes. Mas se você não consegue se lembrar exatamente de como fazer as coisas finde a sobrecarga de invocação não é muito ruim em sua situação, vale a pena lembrar.

OmnipotentEntity
fonte
Muitas vezes eu adicionar um echo $i;no corpo do laço como um "indicador de progresso"
Rolf
3
seq 1 15000 | awk '{print "file_"$0".dat"}' | xargs cat > file_all.pdb
LarryC
fonte
11
awk pode fazer o trabalho de seq aqui e seq pode fazer o trabalho de awk: seq -f file_%.10g.pdb 15000. Observe que seqnão é um comando padrão.
Stéphane Chazelas 27/02
Obrigado Stéphane - Eu acho que seq -f é uma ótima maneira de fazer isso; vai se lembrar disso.
LarryC
2

Premissa

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

Se você estiver executando essa expansão em outro diretório e precisar adicionar o caminho a cada arquivo, o tamanho do seu comando será maior e, é claro, pode ocorrer.

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

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

Melhor solução Se, em vez disso, eu adivinhei mal e você o executou no diretório em que os arquivos estão ...
IMHO, a melhor solução é a 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 seu número pré-armazenado em cache, é ainda o mais rápido (atualmente e exceto o OP do mesmo diretório em que os arquivos estão).

Algumas palavras mais

Você deve poder passar para as linhas de comando do 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

... mesmo adicionar 8 bytes para cada palavra é 333 938 bytes (0,3M) muito abaixo dos 2097142 (2,1M) relatados ARG_MAXno kernel 3.13.0 ou do 2088232 um pouco menor relatado como "Comprimento máximo de comando que poderíamos realmente use " porxargs --show-limits

Dê uma olhada no seu sistema para obter 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 porque geralmente são uma solução eficiente em termos de tempo.
A lógica (se houver) é que estou com preguiça de escrever 1 ... 1000 1001..2000 etc etc ...
Então, peço a um script que faça isso por mim.
Somente depois de verificar a saída, a correção é redirecionada para um script.

... mas a preguiça é um estado de espírito .
Como sou alérgico a xargs(eu realmente deveria ter usado xargsaqui) e não quero verificar como usá-lo, 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 usar facilmente algo como o script abaixo.

tl; dr

Versão 1: passe como parâmetro opcional o 1º número do 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 trabalhar diretamente com as variáveis ​​no bash, usar python ou compilar um programa ac para fazê-lo [ 4 ] ...

Hastur
fonte
Note que %gé a abreviação de %.6g. Representaria 1.000.000 como 1e + 06, por exemplo.
Stéphane Chazelas 27/02
Pessoas realmente preguiçosas usam as ferramentas projetadas para a tarefa de contornar essa limitação do E2BIG xargs, como zsh zargsou ksh93's command -x.
Stéphane Chazelas 27/02
seqnão é um bash embutido, é um comando do GNU coreutils. seq -f %g 1000000 1000000produz 1e + 06 mesmo na versão mais recente do coreutils.
Stéphane Chazelas 27/02
@ StéphaneChazelas A preguiça é um estado de espírito. É estranho dizer, mas me sinto mais confortável quando posso ver (e verificar visualmente a saída de um comando serializado) e só então redirecionar para a execução. Essa construção me fez pensar menos do que xarg... mas eu entendo que é pessoal e talvez relacionado apenas a mim.
Hastur 27/02
@ StéphaneChazelas Gotcha, certo ... Corrigido. Obrigado. Testei apenas com os 15k arquivos fornecidos pelo OP, meu problema.
Hastur 27/02
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
glglgl
fonte