it-swarm.com.de

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

Ich habe ungefähr 15.000 Dateien mit dem Namen file_1.pdb, file_2.pdb usw. Ich kann ungefähr ein paar Tausend davon in der Reihenfolge katzen, indem ich Folgendes tue:

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

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

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

Ich habe gesehen, dass dieses Problem durch find . -name xx -exec xx, aber dies würde die Reihenfolge, in der die Dateien verbunden werden, nicht beibehalten. Wie kann ich das erreichen?

23
sodiumnitrate

Verwenden von find, sort und xargs:

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

Der Befehl find findet alle relevanten Dateien und druckt dann ihre Pfadnamen an sort aus, der eine "Versionssortierung" durchführt, um sie in die richtige Reihenfolge zu bringen (wenn die Zahlen in den Dateinamen Null waren). auf eine feste Breite gefüllt hätten wir nicht gebraucht -V). xargs nimmt diese Liste sortierter Pfadnamen und führt cat auf diesen 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 -print0 Mit find, um sort nicht terminierte Namen zum Sortieren zu geben, und sort behandelt diese mit -z. xargs liest auch nicht terminierte Namen mit dem Flag -0.

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


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 standardmäßigen Flags sind

  • -maxdepth 1, Damit find nur das oberste Verzeichnis, aber keine Unterverzeichnisse eingibt. Verwenden Sie POSIXly find . ! -name . -Prune ...
  • -print0, Damit find nicht terminierte Pfadnamen ausgeben (dies wurde von POSIX berücksichtigt, aber abgelehnt). Man könnte stattdessen -exec printf '%s\0' {} + Verwenden.
  • -z, Damit sort nicht terminierte Datensätze nimmt. Es gibt keine POSIX-Äquivalenz.
  • -V, Um sort sortieren zu lassen, z. 200 Nach 3. Es gibt keine POSIX-Äquivalenz, sie kann jedoch durch eine numerische Sortierung für bestimmte Teile des Dateinamens ersetzt werden, wenn die Dateinamen ein festes Präfix haben.
  • -0, Damit xargs nicht terminierte Datensätze liest. Es gibt keine POSIX-Äquivalenz. POSIXly müsste man die Dateinamen in einem Format zitieren, das von xargs erkannt wird.

Wenn sich die Pfadnamen gut verhalten und die Verzeichnisstruktur flach ist (keine Unterverzeichnisse), könnte man auf diese Flags verzichten, außer auf -V Mit sort.

49
Kusalananda

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

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

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

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

(wobei <x-y> ein Glob-Operator ist, der mit den Dezimalzahlen x bis y übereinstimmt. Ohne x oder y ist es eine beliebige Dezimalzahl. Entspricht extendedglob[0-9]## Oder kshglobs +([0-9]) (eine oder mehrere Ziffern)).

Verwenden Sie bei ksh93 Den integrierten Befehl cat (daher nicht von dieser Grenze des Systemaufrufs execve() betroffen, da kein Ausführung) vorhanden ist:

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

Mit bash/zsh/ksh93 (Die zshs {x..y} Unterstützen und printf eingebaut haben):

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

Auf einem GNU System oder kompatibel) können Sie auch seq verwenden:

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

Bei den auf xargs basierenden Lösungen müsste besonders auf Dateinamen geachtet werden, die Leerzeichen, einfache oder doppelte Anführungszeichen oder umgekehrte Schrägstriche enthalten.

Verwenden Sie wie für -It's a trickier filename - 12.pdb:

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

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 cat verdammt oft aufrufen. Aber wenn Sie sich nicht genau erinnern können, wie Sie das Zeug mit find machen sollen und der Aufrufaufwand in Ihrer Situation nicht so schlimm ist, sollten Sie dies berücksichtigen.

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

Prämisse

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

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

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

(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 Stéphane Chazelas ' :

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

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

Einige Worte mehr

Sie sollten in der Lage sein, länger an Ihre Shell-Befehlszeilen 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 von ARG_MAX auf einem Kernel 3.13.0 gemeldeten Wert von 2097142 (2,1 MB) oder dem etwas kleineren Wert von 2088232, der als - "Maximale Befehlslänge, die wir tatsächlich verwenden können" von xargs --show-limits

Sehen Sie sich die Ausgabe von auf Ihrem System an

getconf ARG_MAX
xargs --show-limits

Faulheit geleitete Lösung

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

... aber Faulheit ist ein Geisteszustand.
Da ich allergisch gegen xargs bin (ich hätte hier wirklich xargs verwenden sollen) und ich nicht überprüfen möchte, wie ich es verwenden soll, beende ich pünktlich, um das Rad neu zu erfinden wie in den folgenden Beispielen (tl; dr).

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

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 (etwas langsamer in meinen Tests ~ 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 vorwärts gehen und seq vollständig loswerden [] (von coreutils) und arbeiten Sie direkt mit den Variablen in bash oder verwenden Sie Python oder kompilieren Sie ein c-Programm, um dies zu tun [ 4 ]...

2
Hastur

Ein anderer Weg könnte es sein

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