katze eine sehr große Anzahl von Dateien zusammen in der richtigen Reihenfolge

23

Ich habe über 15.000 Dateien , die genannt werden file_1.pdb, file_2.pdbusw. Ich kann Katze über ein paar tausend von ihnen um , indem Sie:

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

Wenn ich dies jedoch für 15.000 Dateien tue, wird der Fehler angezeigt

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

Ich habe gesehen, dass dieses Problem dadurch gelöst wird, find . -name xx -exec xxaber dadurch wird die Reihenfolge, in der die Dateien verbunden werden, nicht beibehalten. Wie kann ich das erreichen?

Natriumnitrat
quelle
3
Wie heißt die zehnte Datei? (Oder jede Datei mit mehr als einer einstelligen Nummerierung.)
Roaima
Ich habe (jetzt) ​​15.000 dieser Dateien in einem Verzeichnis und Ihr cat file_{1..15000}.pdbKonstrukt funktioniert einwandfrei für mich.
Roaima
11
hängt vom System ab, wie hoch das Limit ist. getconf ARG_MAXsollte erzählen.
Ilkkachu
3
Ändern Sie Ihre Frage in "Tausende" oder "eine sehr große Anzahl" von Dateien. Könnte es für andere Personen mit ähnlichen Problemen einfacher machen, die Frage zu finden.
Msouth

Antworten:

49

Mit find, sortund xargs:

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

Der findBefehl findet alle relevanten Dateien und gibt dann ihre Pfadnamen aus, sortum sie in der richtigen Reihenfolge zu sortieren (wenn die Zahlen in den Dateinamen auf eine feste Breite mit Nullen aufgefüllt worden wären, hätten wir sie nicht benötigt -V). xargsNimmt diese Liste sortierter Pfadnamen und führt catdiese in möglichst großen Stapeln aus.

Dies sollte auch dann funktionieren, wenn die Dateinamen seltsame Zeichen wie Zeilenumbrüche und Leerzeichen enthalten. Wir verwenden -print0with find, um sortNamen zu sortieren, die mit Nullen abgeschlossen sind, und sortbehandeln diese mithilfe von -z. xargsAuch liest Nul-terminierte Namen mit seinem -0Flag.

Beachten Sie, dass ich das Ergebnis in eine Datei schreibe, deren Name nicht mit dem Muster übereinstimmt file_*.pdb.


Die obige Lösung verwendet einige nicht standardmäßige Flags für einige Dienstprogramme. Diese werden von der GNU-Implementierung dieser Dienstprogramme und zumindest von der OpenBSD- und der macOS-Implementierung unterstützt.

Die verwendeten Nicht-Standard-Flags sind

  • -maxdepth 1, um findnur das oberste Verzeichnis, aber keine Unterverzeichnisse einzugeben. POSIXly verwendenfind . ! -name . -prune ...
  • -print0, um findPfadnamen mit nicht abgeschlossener Ausgabe zu erstellen (dies wurde von POSIX berücksichtigt, aber abgelehnt). Man könnte -exec printf '%s\0' {} +stattdessen verwenden.
  • -z, um sortnicht terminierte Datensätze aufzunehmen. Es gibt keine POSIX-Äquivalenz.
  • -V, sortsortieren zB 200nach 3. Es gibt keine POSIX-Entsprechung, diese kann jedoch durch eine numerische Sortierung bestimmter Teile des Dateinamens ersetzt werden, wenn die Dateinamen ein festes Präfix haben.
  • -0, um nicht xargsabgeschlossene Datensätze zu lesen. Es gibt keine POSIX-Äquivalenz. POSIXly müsste man die Dateinamen in einem Format angeben, das von erkannt wird xargs.

Wenn sich die Pfadnamen gut verhalten und die Verzeichnisstruktur flach ist (keine Unterverzeichnisse), könnte man -Vmit Ausnahme von mit auf diese Flags verzichten sort.

Kusalananda
quelle
1
Hierfür benötigen Sie keine nicht standardmäßige Null-Terminierung. Diese Dateinamen sind überaus langweilig und die POSIX-Tools sind dann durchaus in der Lage, damit umzugehen.
Kevin
6
Sie könnten dies auch prägnanter mit der Angabe des Fragestellers als printf ‘file_%d.pdb\0’ {1..15000} | xargs -0 catoder sogar mit Kevins Punkt schreiben echo file_{1..15000}.pdb | xargs cat. Die findLösung hat erheblich mehr Aufwand, da das Dateisystem nach diesen Dateien durchsucht werden muss, ist jedoch nützlicher, wenn einige der Dateien möglicherweise nicht vorhanden sind.
Kojiro
4
@ Kevin, während das, was Sie sagen, wahr ist, ist es wohl besser, eine Antwort zu haben, die unter allgemeineren Umständen zutrifft. Von den nächsten tausend Personen, die diese Frage haben, ist es wahrscheinlich, dass einige von ihnen Leerzeichen oder was auch immer in ihren Dateinamen haben.
Msouth
1
@chrylis Eine Umleitung ist nie Teil eines Arguments des Befehls, und es ist xargseher als catdass umgeleitet wird (jeder catAufruf wird verwenden xargsStandardausgabe). Wenn wir das gesagt xargs -0 sh -c 'cat >all.pdb'hätten, hätte es Sinn gemacht, >>statt zu verwenden >, wenn Sie das angedeutet haben.
Kusalananda
1
Es sieht so aus, als sort -n -k1.6würde es funktionieren (für das Original, file_nnnDateinamen oder sort -n -k1.5für diejenigen ohne Unterstrich).
Scott
14

Mit zsh(woher dieser {1..15000}Operator kommt):

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

Oder für alle file_<digits>.pdbDateien in numerischer Reihenfolge:

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

(Wo <x-y>ist ein Glob-Operator, der mit den Dezimalzahlen x bis y übereinstimmt? Ohne xNor yist dies eine beliebige Dezimalzahl. Entspricht extendedglob's [0-9]##oder kshglob' s +([0-9])(einer oder mehreren Ziffern)).

Mit ksh93, unter Verwendung seines eingebauten catBefehls (also nicht betroffen von dieser Begrenzung des execve()Systemaufrufs, da es keine Ausführung gibt ):

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

Mit bash/ zsh/ ksh93(die Unterstützung zsh‚s {x..y}und haben printfbuiltin):

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

Auf einem GNU-System oder einem kompatiblen System können Sie auch Folgendes verwenden seq:

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

Bei den xargsLösungen auf der Grundlage von Leerzeichen, einfachen oder doppelten Anführungszeichen oder umgekehrten Schrägstrichen müsste besondere Sorgfalt auf Dateinamen verwendet werden.

Wie für -It's a trickier filename - 12.pdb, verwenden Sie:

seq -f "\"./-It's a trickier filename - %.17g.pdb\"" 15000 |
  xargs cat > file_all.pdb
Stéphane Chazelas
quelle
Dies seq -f | xarg cat > ist die eleganteste und effektivste Lösung. (MEINER BESCHEIDENEN MEINUNG NACH).
Hastur
Überprüfen Sie den schwierigeren Dateinamen ... vielleicht '"./-It'\''s a trickier filename - %.17g.pdb"'?
Hastur
@Hastur, hoppla! Ja, danke, ich habe es in eine alternative Zitiersyntax geändert. Ihr würde auch funktionieren.
Stéphane Chazelas
11

Eine for-Schleife ist möglich und sehr einfach.

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

Der Nachteil ist, dass Sie catsehr oft anrufen . Aber wenn Sie sich nicht genau erinnern können, wie Sie die Dinge anstellen sollen findund der Aufwand für das Aufrufen in Ihrer Situation nicht allzu schlimm ist, sollten Sie dies berücksichtigen.

Allmacht
quelle
Ich füge oft einen echo $i;in der Schleife Körper als "Fortschrittsanzeige"
Rolf
3
seq 1 15000 | awk '{print "file_"$0".dat"}' | xargs cat > file_all.pdb
LarryC
quelle
1
awk kann hier seq Job tun und seq kann awk Job tun: seq -f file_%.10g.pdb 15000. Beachten Sie, dass dies seqkein Standardbefehl ist.
Stéphane Chazelas
Vielen Dank, Stéphane. Ich denke, das seq -f ist eine großartige Möglichkeit. werde mich daran erinnern.
LarryC
2

Prämisse

Dieser Fehler sollte nicht bei nur 15.000 Dateien mit diesem bestimmten Namensformat auftreten [ 1 , 2 ] .

Wenn Sie diese Erweiterung in einem anderen Verzeichnis ausführen und den Pfad zu jeder Datei hinzufügen müssen, ist der Umfang Ihres Befehls größer und kann natürlich auftreten.

Lösung führen Sie den Befehl aus diesem Verzeichnis.

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

Beste Lösung Wenn ich stattdessen schlecht geraten habe und Sie es aus dem Verzeichnis ausführen, in dem sich die Dateien befinden ...
IMHO ist die beste Lösung die von Stéphane Chazelas :

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

mit printf oder seq; Getestet mit 15k-Dateien, bei denen nur deren Nummer im Cache gespeichert ist, ist dies sogar die schnellere (derzeit und mit Ausnahme der OP-Datei aus demselben Verzeichnis, in dem sich die Dateien befinden).

Noch ein paar Worte

Sie sollten in der Lage sein, Ihre Shell-Befehlszeilen länger zu übergeben.
Ihre Befehlszeile ist 213914 Zeichen lang und enthält 15003 Wörter
cat file_{1..15000}.pdb " > file_all.pdb" | wc

... sogar das Hinzufügen von 8 Bytes für jedes Wort liegt 333.938 Bytes (0,3 MB) weit unter dem Wert von 2097142 (2,1 MB), der von ARG_MAXeinem Kernel 3.13.0 oder dem geringfügig kleineren Wert von 2088232 gemeldet wurde, der als "Maximale Befehlslänge, die wir tatsächlich erreichen könnten" angegeben wurde use " vonxargs --show-limits

Sehen Sie sich die Ausgabe von auf Ihrem System an

getconf ARG_MAX
xargs --show-limits

Laziness geführte Lösung

In solchen Fällen arbeite ich am liebsten mit Blöcken, auch weil sich in der Regel eine zeitsparende Lösung ergibt.
Die Logik (falls vorhanden) ist, dass ich viel zu faul bin, um 1 ... 1000 1001..2000 usw. usw. zu schreiben.
Deshalb bitte ich ein Skript, dies für mich zu tun.
Erst nachdem ich die Ausgabe auf Korrektheit überprüft habe, leite ich sie in ein Skript um.

... aber Faulheit ist ein Geisteszustand .
Da ich allergisch gegen xargs(ich hätte es xargshier wirklich verwenden sollen ) bin und nicht prüfen wollen, wie ich es benutze, beende ich die Neuerfindung des Rads pünktlich wie in den folgenden Beispielen (tl; dr).

Da die Dateinamen kontrolliert werden (keine Leerzeichen, Zeilenumbrüche ...), können Sie problemlos mit dem folgenden Skript arbeiten.

tl; dr

Version 1: Übergeben Sie als optionalen Parameter die 1. Dateinummer, die letzte, die Blockgröße und die Ausgabedatei

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

Version 2

Aufruf von bash für die Erweiterung (in meinen Tests etwas langsamer ~ 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;

Natürlich können Sie seq [ 3 ] (von coreutils) loswerden und direkt mit den Variablen in bash arbeiten oder Python verwenden oder ein Programm kompilieren, um dies zu tun [ 4 ] ...

Hastur
quelle
Beachten Sie, dass %gdie Abkürzung für ist %.6g. Es würde zum Beispiel 1.000.000 als 1e + 06 darstellen.
Stéphane Chazelas
Wirklich faul Leute benutzen die Werkzeuge für die Aufgabe entwickelt , den Arbeits um die E2BIG Einschränkung wie xargs, zsh des zargsoder ksh93‚s command -x.
Stéphane Chazelas
seqist kein bash builtin, sondern ein Befehl von GNU coreutils. seq -f %g 1000000 1000000gibt 1e + 06 auch in der neuesten Version von coreutils aus.
Stéphane Chazelas
@ StéphaneChazelas Faulheit ist eine Geisteshaltung. Seltsamerweise, aber ich fühle mich wohler, wenn ich die Ausgabe eines serialisierten Befehls sehen (und visuell überprüfen) und erst dann zur Ausführung umleiten kann. Diese Konstruktion lässt mich weniger nachdenken als xarg... aber ich verstehe, dass sie persönlich ist und vielleicht nur mit mir zusammenhängt.
Hastur
@ StéphaneChazelas Gotcha, richtig ... Behoben. Vielen Dank. Ich habe nur mit den vom OP vorgegebenen 15k-Dateien getestet, mein schlechtes.
Hastur
0

Ein anderer Weg, dies zu tun, könnte sein

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
glglgl
quelle