cat un numero molto grande di file insieme nell'ordine corretto


23

Ho circa 15.000 file che sono nominati file_1.pdb, file_2.pdbecc. Posso cat circa alcune migliaia di questi in ordine facendo:

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

Tuttavia, se lo faccio per 15.000 file, ottengo l'errore

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

Ho visto risolvere questo problema facendo, find . -name xx -exec xxma ciò non preserverebbe l'ordine con cui i file vengono uniti. Come posso raggiungere questo obiettivo?


3
Come si chiama il decimo file? (O qualsiasi file con più di un ordine numerato a una sola cifra.)
roaima,

Ho (ora) 15.000 di questi file in una directory e il tuo cat file_{1..15000}.pdbcostrutto funziona bene per me.
roaima,

11
dipende dal sistema quale sia il limite. getconf ARG_MAXdovrebbe dirlo.
ilkkachu,

3
Valuta di cambiare la tua domanda in "migliaia di" o "un numero molto elevato di" file. Potrebbe rendere la domanda più facile da trovare per altre persone con un problema simile.
msouth,

Risposte:


49

Utilizzando find, sorte xargs:

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

Il findcomando trova tutti i file rilevanti, quindi stampa i loro percorsi su sortun "ordinamento versione" per farli nel giusto ordine (se i numeri nei nomi dei file fossero stati riempiti di zero a una larghezza fissa non avremmo bisogno -V). xargsprende questo elenco di nomi di percorso ordinati e viene eseguito catsu questi in lotti il ​​più grandi possibile.

Questo dovrebbe funzionare anche se i nomi dei file contengono caratteri strani come newline e spazi. Usiamo -print0con findper dare sortnomi nul-terminati da ordinare e li sortgestisce usando -z. xargstroppo legge i nomi nul-terminati con il suo -0flag.

Si noti che sto scrivendo il risultato in un file il cui nome non corrisponde al modello file_*.pdb.


La soluzione di cui sopra utilizza alcuni flag non standard per alcune utility. Questi sono supportati dall'implementazione GNU di queste utility e almeno dall'implementazione OpenBSD e macOS.

I flag non standard utilizzati sono

  • -maxdepth 1, per findinserire solo la directory più in alto ma nessuna sottodirectory. POSIXly, usafind . ! -name . -prune ...
  • -print0, per creare findnomi di percorso nul-terminati (questo è stato considerato da POSIX ma rifiutato). Si potrebbe usare -exec printf '%s\0' {} +invece.
  • -z, per sortprendere record nul-terminati. Non esiste equivalenza POSIX.
  • -V, per fare una sortspecie, ad es . 200dopo 3. Non esiste alcuna equivalenza POSIX, ma potrebbe essere sostituita da un ordinamento numerico su parti specifiche del nome file se i nomi file hanno un prefisso fisso.
  • -0, per xargsleggere i record nul-terminati. Non esiste equivalenza POSIX. POSIXly, bisognerebbe citare i nomi dei file in un formato riconosciuto da xargs.

Se i nomi dei percorsi sono ben educati e se la struttura della directory è piatta (nessuna sottodirectory), allora si potrebbe accontentarsi di questi flag, tranne che -Vcon sort.


1
Non hai bisogno di una terminazione nulla non standard per questo. Questi nomi di file sono estremamente noiosi e gli strumenti POSIX sono completamente in grado di gestirli.
Kevin,

6
Si potrebbe anche scrivere questo più succintamente con le specifiche del richiedente come printf ‘file_%d.pdb\0’ {1..15000} | xargs -0 cat, o addirittura con il punto di Kevin, echo file_{1..15000}.pdb | xargs cat. La findsoluzione presenta un sovraccarico considerevolmente maggiore poiché deve cercare tali file nel file system, ma è più utile quando alcuni dei file potrebbero non esistere.
Kojiro,

4
@Kevin mentre quello che stai dicendo è vero, è probabilmente meglio avere una risposta che si applica in circostanze più generali. Delle prossime migliaia di persone che hanno questa domanda, è probabile che alcuni di loro avranno spazi o altro nei loro nomi di file.
msouth,

1
@chrylis Un reindirizzamento non fa mai parte degli argomenti di un comando ed è xargspiuttosto catche reindirizzato (ogni catinvocazione utilizzerà xargsl'output standard). Se avessimo detto xargs -0 sh -c 'cat >all.pdb'che avrebbe avuto senso usare >>invece di >, se è quello che stai suggerendo.
Kusalananda

1
Sembra sort -n -k1.6che funzionerebbe (per l'originale, i file_nnnnomi dei file o sort -n -k1.5per quelli senza il carattere di sottolineatura).
Scott,

14

Con zsh(da dove {1..15000}proviene quell'operatore):

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

O per tutti i file_<digits>.pdbfile in ordine numerico:

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

(dove <x-y>è un operatore glob che corrisponde ai numeri decimali da x a y. Con no xy, è un numero decimale. Equivalente a extendedglob' [0-9]##o kshglob' +([0-9])(una o più cifre)).

Con ksh93, usando il suo catcomando incorporato (quindi non influenzato da quel limite della execve()chiamata di sistema poiché non c'è esecuzione ):

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

Con bash/ zsh/ ksh93(che il supporto zshs' {x..y}e hanno printfintegrato):

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

Su un sistema GNU o compatibile, puoi anche usare seq:

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

Per le xargssoluzioni basate su, si dovrebbe prestare particolare attenzione ai nomi dei file che contengono spazi vuoti, virgolette singole o doppie o barre rovesciate.

Come per -It's a trickier filename - 12.pdb, usare:

seq -f "\"./-It's a trickier filename - %.17g.pdb\"" 15000 |
  xargs cat > file_all.pdb

La seq -f | xarg cat > è la soluzione più elegante ed efficace. (A PARER MIO).
Hastur,

Controlla il nome del file più complicato ... forse '"./-It'\''s a trickier filename - %.17g.pdb"'?
Hastur,

@Hastur, oops! Sì, grazie, l'ho cambiato in una sintassi di quotazione alternativa. Anche il tuo funzionerebbe.
Stéphane Chazelas,

11

Un ciclo for è possibile e molto semplice.

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

Il rovescio della medaglia è che invochi catun sacco di volte. Ma se non riesci a ricordare esattamente come fare le cose finde il sovraccarico di invocazione non è troppo male nella tua situazione, allora vale la pena tenere a mente.


Aggiungo spesso a echo $i;nel corpo del loop come un "indicatore di progresso"
Rolf

3
seq 1 15000 | awk '{print "file_"$0".dat"}' | xargs cat > file_all.pdb

1
awk può fare il lavoro di ss qui e ss può fare il lavoro di awk: seq -f file_%.10g.pdb 15000. Si noti che seqnon è un comando standard.
Stéphane Chazelas,

Grazie Stéphane - Penso che seq -f sia un ottimo modo per farlo; lo ricorderò.
LarryC,

2

Premessa

Non dovresti incorrere in questo errore solo per 15k file con quel formato di nome specifico [ 1 , 2 ] .

Se stai eseguendo tale espansione da un'altra directory e devi aggiungere il percorso a ciascun file, la dimensione del tuo comando sarà più grande e, naturalmente, può verificarsi.

La soluzione esegue il comando da quella directory.

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

La migliore soluzione Se invece ho indovinato e lo esegui dalla directory in cui sono i file ...
IMHO la soluzione migliore è quella di Stéphane Chazelas :

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

con printf o seq; testato su file 15k con solo il loro numero all'interno pre-memorizzato nella cache, è anche il più veloce (al momento e ad eccezione di quello OP dalla stessa directory in cui si trovano i file).

Qualche parola in più

Dovresti essere in grado di passare più a lungo alle righe di comando della shell.
La riga di comando è lunga 213914 caratteri e contiene 15003 parole
cat file_{1..15000}.pdb " > file_all.pdb" | wc

... anche l'aggiunta di 8 byte per ogni parola è di 333 938 byte (0,3 M) molto al di sotto del 2097142 (2,1 M) riportato da ARG_MAXun kernel 3.13.0 o del 2088232 leggermente più piccolo riportato come "Lunghezza massima del comando che potremmo effettivamente usa " dixargs --show-limits

Dai un'occhiata al tuo sistema all'output di

getconf ARG_MAX
xargs --show-limits

Soluzione guidata per pigrizia

In casi come questo preferisco lavorare con i blocchi anche perché di solito viene fuori una soluzione efficiente in termini di tempo.
La logica (se presente) è che sono troppo pigro per scrivere 1 ... 1000 1001..2000 ecc ecc ...
Quindi chiedo a uno script di farlo per me.
Solo dopo aver verificato che l'output è corretto, lo reindirizzo a uno script.

... ma la pigrizia è uno stato d'animo .
Dato che sono allergico a xargs(avrei dovuto usarlo xargsqui) e non voglio controllare come usarlo, finisco puntualmente di reinventare la ruota come negli esempi seguenti (tl; dr).

Si noti che poiché i nomi dei file sono controllati (senza spazi, newline ...) puoi andare facilmente con qualcosa come lo script qui sotto.

tl; dr

Versione 1: passa come parametro opzionale il primo numero di file, l'ultimo, la dimensione del blocco, il file di output

#!/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;

Versione 2

Chiamando bash per l'espansione (un po 'più lento nei miei test ~ 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;

Ovviamente puoi andare avanti e liberarti completamente di seq [ 3 ] (da coreutils) e lavorare direttamente con le variabili in bash, oppure usare python o compilare un programma ac per farlo [ 4 ] ...


Si noti che %gè l'abbreviazione di %.6g. Rappresenterebbe 1.000.000 come 1e + 06 per esempio.
Stéphane Chazelas,

Le persone veramente pigre usano gli strumenti progettati per aggirare quella limitazione di E2BIG come xargs, zsh's zargso ksh93's command -x.
Stéphane Chazelas,

seqnon è un built-in bash, è un comando da coreutils GNU. seq -f %g 1000000 1000000produce 1e + 06 anche nell'ultima versione di coreutils.
Stéphane Chazelas,

@ StéphaneChazelas La pigrizia è uno stato d'animo. Strano a dirsi ma mi sento più a mio agio quando posso vedere (e controllare visivamente l'output di un comando serializzato) e solo allora reindirizzare all'esecuzione. Quella costruzione mi fa pensare meno di xarg... ma capisco che è personale e forse legato solo a me.
Hastur,

@ StéphaneChazelas Gotcha, giusto ... Risolto. Grazie. Ho provato solo con i file 15k forniti dall'OP, mio ​​male.
Hastur,

0

Un altro modo per farlo potrebbe essere

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
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.