Wenn es darum geht, eine Textdatei unter Linux in mehrere Dateien aufzuteilen, verwenden die meisten Leute den Split-Befehl. Am Split-Befehl ist nichts auszusetzen, außer dass er sich auf die Bytegröße oder Zeilengröße zum Aufteilen der Dateien verlässt.
Dies ist in Situationen nicht praktisch, in denen Sie Dateien nach ihrem Inhalt statt nach ihrer Größe aufteilen müssen. Lassen Sie mich Ihnen ein Beispiel geben.
Ich verwalte meine geplanten Tweets mithilfe von YAML-Dateien. Eine typische Tweet-Datei enthält mehrere Tweets, die durch vier Bindestriche getrennt sind:
----
event:
repeat: { days: 180 }
status: |
I think I use the `sed` command daily. And you?
https://www.yesik.it/EP07
#Shell #Linux #Sed #YesIKnowIT
----
status: |
Print the first column of a space-separated data file:
awk '{print $1}' data.txt # Print out just the first column
For some unknown reason, I find that easier to remember than:
cut -f1 data.txt
#Linux #AWK #Cut
----
status: |
For the #shell #beginners :
[...]
Wenn ich sie in mein System importiere, muss ich jeden Tweet in eine eigene Datei schreiben. Ich mache das, um zu vermeiden, dass doppelte Tweets registriert werden.
Aber wie kann man eine Datei basierend auf ihrem Inhalt in mehrere Teile aufteilen? Nun, wahrscheinlich können Sie mit awk-Befehlen etwas Überzeugendes erreichen:
sh$ awk < tweets.yaml '
> /----/ { OUTPUT="tweet." (N++) ".yaml" }
> { print > OUTPUT }
> '
Trotz relativer Einfachheit ist eine solche Lösung jedoch nicht sehr robust:Beispielsweise habe ich die verschiedenen Ausgabedateien nicht richtig geschlossen, sodass dies sehr wohl die Grenze der offenen Dateien erreichen könnte. Oder was ist, wenn ich das Trennzeichen vor dem allerersten Tweet der Datei vergessen habe? Natürlich kann all das im AWK-Skript gehandhabt und behoben werden, auf Kosten der Komplexität. Aber warum sollte man sich damit beschäftigen, wenn wir den csplit
haben Werkzeug, um diese Aufgabe zu erfüllen?
Mit csplit Dateien unter Linux aufteilen
Der csplit
tool ist ein Cousin von split
Werkzeug, das verwendet werden kann, um eine Datei in feste Größe aufzuteilen Brocken. Aber csplit
wird die Chunk-Grenzen basierend auf dem Dateiinhalt identifizieren, anstatt die Byte-Anzahl zu verwenden.
In diesem Tutorial demonstriere ich die Verwendung des csplit-Befehls und erkläre auch die Ausgabe dieses Befehls.
Wenn ich also zum Beispiel meine Tweet-Datei basierend auf ----
aufteilen möchte Trennzeichen könnte ich schreiben:
sh$ csplit tweets.yaml /----/
0
10846
Sie haben vielleicht den csplit
erraten Das Tool hat den in der Befehlszeile bereitgestellten regulären Ausdruck verwendet, um das Trennzeichen zu identifizieren. Und was könnten diese 0
sein und 10983
Ergebnis auf der Standardausgabe angezeigt? Nun, sie haben die Größe in Bytes jedes erstellten Datenblocks.
sh$ ls -l xx0*
-rw-r--r-- 1 sylvain sylvain 0 Jun 6 11:30 xx00
-rw-r--r-- 1 sylvain sylvain 10846 Jun 6 11:30 xx01
Warte eine Minute! Wo diese xx00
und xx01
Dateinamen kommen aus? Und warum csplit
Teilen Sie die Datei in nur zwei Teile auf ? Und warum der erste Datenchunk eine Länge von null Bytes hat ?
Die Antwort auf die erste Frage ist einfach:xxNN
(oder formeller xx%02d
) ist das standardmäßige Dateinamenformat, das von csplit
verwendet wird . Aber Sie können das mit dem --suffix-format
ändern und --prefix
Optionen. Zum Beispiel könnte ich das Format in etwas sinnvolleres für meine Bedürfnisse ändern:
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> /----/
0
10846
sh$ ls -l tweet.*
-rw-r--r-- 1 sylvain sylvain 0 Jun 6 11:30 tweet.000.yaml
-rw-r--r-- 1 sylvain sylvain 10846 Jun 6 11:30 tweet.001.yaml
Das Präfix ist eine einfache Zeichenfolge, aber das Suffix ist eine Formatzeichenfolge, wie sie von der Standard-C-Bibliothek printf
verwendet wird Funktion. Die meisten Zeichen des Formats werden wörtlich verwendet, mit Ausnahme von Konvertierungsspezifikationen, die durch das Prozentzeichen (%
) und die mit einem Konvertierungsbezeichner endet (hier d
). Dazwischen kann das Format auch diverse Flags und Optionen enthalten. In meinem Beispiel der %03d
Konvertierungsspezifikation bedeutet:
- Anzeige der Chunk-Nummer als Dezimalzahl (
d
), - in einem drei Zeichen breiten Feld (
3
), - eventuell links mit Nullen aufgefüllt (
0
).
Aber das spricht nicht die anderen Befragungen an, die ich oben hatte:Warum haben wir nur zwei Chunks, von denen einer Null enthält Bytes? Vielleicht haben Sie die letzte Frage schon selbst beantwortet:Meine Datendatei beginnt mit ----
in seiner allerersten Zeile. Also csplit
betrachtete es als Trennzeichen, und da es vor dieser Zeile keine Daten gab, erstellte es einen leeren ersten Block. Wir können die Erstellung von Dateien mit einer Länge von null Bytes mit --elide-empty-files
deaktivieren Möglichkeit:
sh$ rm tweet.*
rm: cannot remove 'tweet.*': No such file or directory
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> /----/
10846
sh$ ls -l tweet.*
-rw-r--r-- 1 sylvain sylvain 10846 Jun 6 11:30 tweet.000.yaml
Ok:keine leeren Dateien mehr. Aber in gewisser Weise ist das Ergebnis jetzt das schlechteste seit csplit
Teilen Sie die Datei in nur eine auf Stück. Wir können das kaum als „Aufteilen“ einer Datei bezeichnen, oder?
Die Erklärung für dieses überraschende Ergebnis ist csplit
tut nicht Gehen Sie überhaupt davon aus, dass jedes Spannfutter auf der Grundlage des gleichen geteilt werden sollte Separator. Eigentlich csplit
erfordert, dass Sie jedes verwendete Trennzeichen angeben. Auch wenn es mehrmals dasselbe ist:
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> /----/ /----/ /----/
170
250
10426
Ich habe drei (identische) Trennzeichen in die Befehlszeile eingefügt. Also csplit
identifiziert das Ende des ersten Chunks basierend auf dem ersten Trennzeichen. Dies führt zu einem Stück mit einer Länge von null Bytes, das entfernt wurde. Der zweite Block wurde durch die nächste Zeile begrenzt, die mit /----/
übereinstimmte . Führt zu einem 170-Byte-Stück. Schließlich wurde basierend auf dem dritten Trennzeichen ein dritter Block mit einer Länge von 250 Bytes identifiziert. Die restlichen Daten, 10426 Bytes, wurden in den letzten Chunk gelegt.
sh$ ls -l tweet.???.yaml
-rw-r--r-- 1 sylvain sylvain 170 Jun 6 11:30 tweet.000.yaml
-rw-r--r-- 1 sylvain sylvain 250 Jun 6 11:30 tweet.001.yaml
-rw-r--r-- 1 sylvain sylvain 10426 Jun 6 11:30 tweet.002.yaml
Offensichtlich wäre es nicht praktikabel, wenn wir so viele Trennzeichen in der Befehlszeile angeben müssten, wie Chunks in der Datendatei vorhanden sind. Zumal diese genaue Zahl meist nicht im Voraus bekannt ist. Glücklicherweise csplit
hat ein spezielles Muster, das bedeutet "das vorherige Muster so oft wie möglich wiederholen." Trotz seiner Syntax, die an den Sternquantifizierer in einem regulären Ausdruck erinnert, ist dies näher am Kleene-Plus-Konzept, da es verwendet wird, um ein Trennzeichen zu wiederholen, das bereits hat wurde einmal abgeglichen:
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> /----/ '{*}'
170
250
190
208
140
[...]
247
285
194
214
185
131
316
221
Und diesmal habe ich endlich meine Tweet-Sammlung in einzelne Teile aufgeteilt. Allerdings tut csplip
Haben Sie noch andere schöne „spezielle“ Muster wie dieses? Nun, ich weiß nicht, ob wir sie „besonders“ nennen können, aber auf jeden Fall csplit
Muster besser verstehen.
Weitere csplit-Muster
Wir haben gerade im vorherigen Abschnitt gesehen, wie man den Quantifizierer „{*}“ für ungebundene Wiederholungen verwendet. Indem Sie jedoch den Stern durch eine Zahl ersetzen, können Sie eine genaue Anzahl von Wiederholungen anfordern:
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> /----/ '{6}'
170
250
190
208
140
216
9672
Das führt zu einem interessanten Eckfall. Was würde angehängt, wenn die Anzahl der Wiederholungen die Anzahl der tatsächlichen Trennzeichen in der Datendatei überschreitet? Sehen wir uns das an einem Beispiel an:
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> /----/ '{999}'
csplit: ‘/----/’: match not found on repetition 62
170
250
190
208
[...]
91
247
285
194
214
185
131
316
221
sh$ ls tweet.*
ls: cannot access 'tweet.*': No such file or directory
Interessanterweise nicht nur csplit
hat einen Fehler gemeldet, aber auch alle entfernt die während des Vorgangs erstellten Chunk-Dateien. Achten Sie besonders auf meine Formulierung:es entfernt Sie. Das heißt, die Dateien wurden dann beim csplit
erstellt auf den Fehler gestoßen, löschte es sie. Mit anderen Worten, wenn Sie bereits eine Datei haben, deren Name wie eine Chunk-Datei aussieht, wird sie entfernt:
sh$ touch tweet.002.yaml
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> /----/ '{999}'
csplit: ‘/----/’: match not found on repetition 62
170
250
190
[...]
87
91
247
285
194
214
185
131
316
221
sh$ ls tweet.*
ls: cannot access 'tweet.*': No such file or directory
Im obigen Beispiel die tweet.002.yaml
Datei, die wir manuell erstellt haben, wurde überschrieben und dann von csplit
entfernt .
Sie können dieses Verhalten mit --keep-files
ändern Möglichkeit. Wie der Name schon sagt, wird es keine csplit-Chunks entfernen, die nach Auftreten eines Fehlers erstellt wurden:
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> --keep-files \
> /----/ '{999}'
csplit: ‘/----/’: match not found on repetition 62
170
250
190
[...]
316
221
sh$ ls tweet.*
tweet.000.yaml
tweet.001.yaml
tweet.002.yaml
tweet.003.yaml
[...]
tweet.058.yaml
tweet.059.yaml
tweet.060.yaml
tweet.061.yaml
Beachten Sie in diesem Fall und trotz des Fehlers csplit
hat keine Daten verworfen:
sh$ diff -s tweets.yaml <(cat tweet.*)
Files tweets.yaml and /dev/fd/63 are identical
Aber was ist, wenn die Datei einige Daten enthält, die ich verwerfen möchte? Nun, csplit
hat eine begrenzte Unterstützung dafür mit einem %regex%
Muster.
Überspringen von Daten in csplit
Bei Verwendung eines Prozentzeichens (%
) als Regex-Trennzeichen anstelle eines Schrägstrichs (/
), csplit
wird übersprungen Daten bis (aber nicht einschließlich) der ersten Zeile, die dem regulären Ausdruck entspricht. Dies kann nützlich sein, um einige Datensätze zu ignorieren, insbesondere am Anfang oder Ende der Eingabedatei:
sh$ # Keep only the first two tweets
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> --keep-files \
> /----/ '{2}' %----% '{*}'
170
250
sh$ head tweet.00[012].yaml
==> tweet.000.yaml <==
----
event:
repeat: { days: 180 }
status: |
I think I use the `sed` command daily. And you?
https://www.yesik.it/EP07
#Shell #Linux #Sed #YesIKnowIT
==> tweet.001.yaml <==
----
status: |
Print the first column of a space-separated data file:
awk '{print $1}' data.txt # Print out just the first column
For some unknown reason, I find that easier to remember than:
cut -f1 data.txt
#Linux #AWK #Cut
sh$ # Skip the first two tweets
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> --keep-files \
> %----% '{2}' /----/ '{2}'
190
208
140
9888
sh$ head tweet.00[012].yaml
==> tweet.000.yaml <==
----
status: |
For the #shell #beginners :
« #GlobPatterns : how to move hundreds of files in not time [1/3] »
https://youtu.be/TvW8DiEmTcQ
#Unix #Linux
#YesIKnowIT
==> tweet.001.yaml <==
----
status: |
Want to know the oldest file in your disk?
find / -type f -printf '%TFT%.8TT %p\n' | sort | less
(should work on any Single UNIX Specification compliant system)
#UNIX #Linux
==> tweet.002.yaml <==
----
status: |
When using the find command, use `-iname` instead of `-name` for case-insensitive search
#Unix #Linux #Shell #Find
sh$ # Keep only the third and fourth tweets
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> --keep-files \
> %----% '{2}' /----/ '{2}' %----% '{*}'
190
208
140
sh$ head tweet.00[012].yaml
==> tweet.000.yaml <==
----
status: |
For the #shell #beginners :
« #GlobPatterns : how to move hundreds of files in not time [1/3] »
https://youtu.be/TvW8DiEmTcQ
#Unix #Linux
#YesIKnowIT
==> tweet.001.yaml <==
----
status: |
Want to know the oldest file in your disk?
find / -type f -printf '%TFT%.8TT %p\n' | sort | less
(should work on any Single UNIX Specification compliant system)
#UNIX #Linux
==> tweet.002.yaml <==
----
status: |
When using the find command, use `-iname` instead of `-name` for case-insensitive search
#Unix #Linux #Shell #Find
Verwendung von Offsets beim Teilen von Dateien mit csplit
Bei der Verwendung regulärer Ausdrücke (entweder /…/
oder %…%
) können Sie ein positives Zeichen angeben (+N
) oder negativ (-N
) versetzt am Ende des Musters, also csplit
teilt die Datei N Zeilen nach oder vor der übereinstimmenden Zeile. Denken Sie daran, dass das Muster in allen Fällen das Ende angibt des Chunks:
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> --keep-files \
> %----%+1 '{2}' /----/+1 '{2}' %----% '{*}'
190
208
140
sh$ head tweet.00[012].yaml
==> tweet.000.yaml <==
status: |
For the #shell #beginners :
« #GlobPatterns : how to move hundreds of files in not time [1/3] »
https://youtu.be/TvW8DiEmTcQ
#Unix #Linux
#YesIKnowIT
----
==> tweet.001.yaml <==
status: |
Want to know the oldest file in your disk?
find / -type f -printf '%TFT%.8TT %p\n' | sort | less
(should work on any Single UNIX Specification compliant system)
#UNIX #Linux
----
==> tweet.002.yaml <==
status: |
When using the find command, use `-iname` instead of `-name` for case-insensitive search
#Unix #Linux #Shell #Find
----
Aufteilen nach Zeilennummer
Wir haben bereits gesehen, wie wir einen regulären Ausdruck verwenden können, um Dateien aufzuteilen. In diesem Fall csplit
teilt die Datei in der ersten Zeile matching diese Regex. Sie können die geteilte Linie aber auch anhand ihrer Liniennummer identifizieren, wie wir sie jetzt sehen werden.
Vor dem Wechsel zu YAML habe ich meine geplanten Tweets in einer Flatfile gespeichert.
In dieser Datei bestand ein Tweet aus zwei Zeilen. Einer enthält eine optionale Wiederholung und der zweite den Text des Tweets, wobei Zeilenumbrüche durch \n ersetzt werden. Wieder einmal ist diese Beispieldatei online verfügbar.
Mit diesem Format mit „fester Größe“ konnte auch csplit
verwendet werden um jeden einzelnen Tweet in eine eigene Datei zu packen:
sh$ csplit tweets.txt \
> --prefix='tweet.' --suffix-format='%03d.txt' \
> --elide-empty-files \
> --keep-files \
> 2 '{*}'
csplit: ‘2’: line number out of range on repetition 62
1
123
222
161
182
119
184
81
148
128
142
101
107
[...]
sh$ diff -s tweets.txt <(cat tweet.*.txt)
Files tweets.txt and /dev/fd/63 are identical
sh$ head tweet.00[012].txt
==> tweet.000.txt <==
==> tweet.001.txt <==
{ days:180 }
I think I use the `sed` command daily. And you?\n\nhttps://www.yesik.it/EP07\n#Shell #Linux #Sed\n#YesIKnowIT
==> tweet.002.txt <==
{}
Print the first column of a space-separated data file:\nawk '{print $1}' data.txt # Print out just the first column\n\nFor some unknown reason, I find that easier to remember than:\ncut -f1 data.txt\n\n#Linux #AWK #Cut
Das obige Beispiel scheint einfach zu verstehen, aber hier gibt es zwei Fallstricke. Zuerst die 2
als Argument an csplit
übergeben ist eine Zeilen-Nummer , keine Zeilen-Anzahl . Wenn ich jedoch eine Wiederholung verwende, wie ich es getan habe, nach der ersten Übereinstimmung, csplit
verwendet diese Zahl als Zeilen-Anzahl . Wenn es nicht klar ist, lasse ich Sie die Ausgabe der drei folgenden Befehle vergleichen:
sh$ csplit tweets.txt --keep-files 2 2 2 2 2
csplit: warning: line number ‘2’ is the same as preceding line number
csplit: warning: line number ‘2’ is the same as preceding line number
csplit: warning: line number ‘2’ is the same as preceding line number
csplit: warning: line number ‘2’ is the same as preceding line number
1
0
0
0
0
9030
sh$ csplit tweets.txt --keep-files 2 4 6 8 10
1
123
222
161
182
8342
sh$ csplit tweets.txt --keep-files 2 '{4}'
1
123
222
161
182
8342
Ich erwähnte eine zweite Falle, die etwas mit der ersten verwandt ist. Vielleicht ist Ihnen die leere Zeile ganz oben in der tweets.txt
aufgefallen Datei? Es führt zu dieser tweet.000.txt
Chunk, der nur das Newline-Zeichen enthält. Leider war es in diesem Beispiel wegen der Wiederholung erforderlich:Denken Sie daran, ich möchte zwei Zeilen Brocken. Also die 2
ist vor der Wiederholung obligatorisch. Das bedeutet aber auch das erste Chunk wird bei brechen, aber nicht einschließlich , die Linie zwei. Mit anderen Worten, der erste Chunk enthält eine Zeile. Alle anderen enthalten 2 Zeilen. Vielleicht könnten Sie Ihre Meinung im Kommentarbereich teilen, aber ich persönlich denke, dass dies eine unglückliche Designwahl war.
Sie können dieses Problem beheben, indem Sie direkt zur ersten nicht leeren Zeile springen:
sh$ csplit tweets.txt \
> --prefix='tweet.' --suffix-format='%03d.txt' \
> --elide-empty-files \
> --keep-files \
> %.% 2 '{*}'
csplit: ‘2’: line number out of range on repetition 62
123
222
161
[...]
sh$ head tweet.00[012].txt
==> tweet.000.txt <==
{ days:180 }
I think I use the `sed` command daily. And you?\n\nhttps://www.yesik.it/EP07\n#Shell #Linux #Sed\n#YesIKnowIT
==> tweet.001.txt <==
{}
Print the first column of a space-separated data file:\nawk '{print $1}' data.txt # Print out just the first column\n\nFor some unknown reason, I find that easier to remember than:\ncut -f1 data.txt\n\n#Linux #AWK #Cut
==> tweet.002.txt <==
{}
For the #shell #beginners :\n« #GlobPatterns : how to move hundreds of files in not time [1/3] »\nhttps://youtu.be/TvW8DiEmTcQ\n\n#Unix #Linux\n#YesIKnowIT
Lesen von stdin
Natürlich, wie die meisten Kommandozeilen-Tools, csplit
kann die Eingabedaten von seiner Standardeingabe lesen. In diesem Fall müssen Sie -
angeben als Eingabedateiname:
sh$ tr [:lower:] [:upper:] < tweets.txt | csplit - \
> --prefix='tweet.' --suffix-format='%03d.txt' \
> --elide-empty-files \
> --keep-files \
> %.% 2 '{3}'
123
222
161
8524
sh$ head tweet.???.txt
==> tweet.000.txt <==
{ DAYS:180 }
I THINK I USE THE `SED` COMMAND DAILY. AND YOU?\N\NHTTPS://WWW.YESIK.IT/EP07\N#SHELL #LINUX #SED\N#YESIKNOWIT
==> tweet.001.txt <==
{}
PRINT THE FIRST COLUMN OF A SPACE-SEPARATED DATA FILE:\NAWK '{PRINT $1}' DATA.TXT # PRINT OUT JUST THE FIRST COLUMN\N\NFOR SOME UNKNOWN REASON, I FIND THAT EASIER TO REMEMBER THAN:\NCUT -F1 DATA.TXT\N\N#LINUX #AWK #CUT
==> tweet.002.txt <==
{}
FOR THE #SHELL #BEGINNERS :\N« #GLOBPATTERNS : HOW TO MOVE HUNDREDS OF FILES IN NOT TIME [1/3] »\NHTTPS://YOUTU.BE/TVW8DIEMTCQ\N\N#UNIX #LINUX\N#YESIKNOWIT
==> tweet.003.txt <==
{}
WANT TO KNOW THE OLDEST FILE IN YOUR DISK?\N\NFIND / -TYPE F -PRINTF '%TFT%.8TT %P\N' | SORT | LESS\N(SHOULD WORK ON ANY SINGLE UNIX SPECIFICATION COMPLIANT SYSTEM)\N#UNIX #LINUX
{}
WHEN USING THE FIND COMMAND, USE `-INAME` INSTEAD OF `-NAME` FOR CASE-INSENSITIVE SEARCH\N#UNIX #LINUX #SHELL #FIND
{}
FROM A POSIX SHELL `$OLDPWD` HOLDS THE NAME OF THE PREVIOUS WORKING DIRECTORY:\NCD /TMP\NECHO YOU ARE HERE: $PWD\NECHO YOU WERE HERE: $OLDPWD\NCD $OLDPWD\N\N#UNIX #LINUX #SHELL #CD
{}
FROM A POSIX SHELL, "CD" IS A SHORTHAND FOR CD $HOME\N#UNIX #LINUX #SHELL #CD
{}
HOW TO MOVE HUNDREDS OF FILES IN NO TIME?\NUSING THE FIND COMMAND!\N\NHTTPS://YOUTU.BE/ZMEFXJYZAQK\N#UNIX #LINUX #MOVE #FILES #FIND\N#YESIKNOWIT
Und das ist so ziemlich alles, was ich dir heute zeigen wollte. Ich hoffe, dass Sie in Zukunft csplit verwenden werden, um Dateien unter Linux aufzuteilen. Wenn Ihnen dieser Artikel gefallen hat, vergessen Sie nicht, ihn in Ihrem bevorzugten sozialen Netzwerk zu teilen und zu liken!