Linuxでテキストファイルを複数のファイルに分割する場合、ほとんどの人はsplitコマンドを使用します。ファイルを分割するためにバイトサイズまたは行サイズに依存することを除いて、splitコマンドに問題はありません。
これは、サイズではなくコンテンツに基づいてファイルを分割する必要がある状況では便利ではありません。例を挙げましょう。
YAMLファイルを使用してスケジュールされたツイートを管理します。一般的なツイートファイルには、4つのダッシュで区切られた複数のツイートが含まれています。
----
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 :
[...]
それらをシステムにインポートするときは、各ツイートを独自のファイルに書き込む必要があります。重複したツイートを登録しないようにするためです。
しかし、ファイルをその内容に基づいていくつかの部分に分割するにはどうすればよいですか?まあ、おそらくawkコマンドを使用して説得力のあるものを得ることができます:
sh$ awk < tweets.yaml '
> /----/ { OUTPUT="tweet." (N++) ".yaml" }
> { print > OUTPUT }
> '
ただし、比較的単純であるにもかかわらず、このようなソリューションはそれほど堅牢ではありません。たとえば、さまざまな出力ファイルを適切に閉じなかったため、開いているファイルの制限に達する可能性があります。または、ファイルの最初のツイートの前にセパレータを忘れた場合はどうなりますか?もちろん、AWKスクリプトで処理および修正できるものはすべてありますが、より複雑になります。しかし、csplit
があるのに、なぜそれを気にするのですか? そのタスクを実行するためのツール?
csplitを使用してLinuxでファイルを分割する
csplit
ツールはsplit
のいとこです ファイルを固定サイズに分割するために使用できるツール チャンク。しかし、csplit
バイトカウントを使用するのではなく、ファイルの内容に基づいてチャンクの境界を識別します。
このチュートリアルでは、csplitコマンドの使用法を示し、このコマンドの出力についても説明します。
たとえば、----
に基づいてツイートファイルを分割したい場合 区切り文字、私は書くことができます:
sh$ csplit tweets.yaml /----/
0
10846
csplit
を推測したかもしれません ツールは、コマンドラインで提供される正規表現を使用してセパレーターを識別しました。そして、それらの0
は何でしょうか および10983
結果は標準出力に表示されますか?ええと、それらはバイトのサイズです 作成された各データチャンクの。
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
ちょっと待って!それらのxx00
およびxx01
ファイル名はどこから来ていますか?そして、なぜcsplit
ファイルを2つのチャンクのみに分割します ?そして、最初のデータチャンクの長さがゼロバイトである理由 ?
最初の質問への答えは簡単です:xxNN
(またはより正式にはxx%02d
)は、csplit
で使用されるデフォルトのファイル名形式です。 。ただし、--suffix-format
を使用して変更できます および--prefix
オプション。たとえば、フォーマットを自分のニーズにとってより意味のあるものに変更できます。
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
プレフィックスはプレーン文字列ですが、サフィックスは標準Cライブラリprintf
で使用されるようなフォーマット文字列です。 働き。パーセント記号(%
で導入される変換仕様を除き、この形式のほとんどの文字はそのまま使用されます。 )そしてこれは変換指定子(ここではd
)で終わります )。その間に、フォーマットにはさまざまなフラグとオプションが含まれる場合もあります。私の例では、%03d
変換仕様とは:
- チャンク番号を10進整数として表示します(
d
)、 - 3文字の幅のフィールド(
3
)、 - 最終的に左側にゼロが埋め込まれます(
0
。
しかし、それは私が上で持っていた他の尋問に対処していません:なぜ私たちは 2つだけあるのですか? チャンク、そのうちの1つにはゼロが含まれています バイト?後者の質問に対する答えを自分ですでに見つけているかもしれません。私のデータファイルは----
で始まります。 その最初の行に。したがって、csplit
これを区切り文字と見なし、その行の前にデータがなかったため、空の最初のチャンクを作成しました。 --elide-empty-files
を使用して、長さが0バイトのファイルの作成を無効にできます。 オプション:
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:空のファイルはもうありません。しかし、ある意味では、csplit
なので、結果は今では最悪です。 ファイルを1つに分割します チャンク。それをファイルの「分割」と呼ぶことはほとんどできませんね。
その驚くべき結果の説明はcsplit
です しない 同じに基づいて各チャックを分割する必要があると想定します。 セパレータ。実際、csplit
使用する各セパレータを用意する必要があります。何度か同じでも:
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> /----/ /----/ /----/
170
250
10426
コマンドラインに3つの(同一の)区切り文字を配置しました。したがって、csplit
最初のセパレータに基づいて最初のチャンクの終わりを識別しました。これにより、長さが0バイトのチャンクが削除されました。 2番目のチャンクは、/----/
に一致する次の行で区切られています 。 170バイトのチャンクにつながります。最後に、3番目の区切り文字に基づいて3番目の250バイト長のチャンクが識別されました。残りのデータ、10426バイトは、最後のチャンクに入れられました。
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
明らかに、データファイルにチャンクがあるのと同じ数の区切り文字をコマンドラインに指定する必要がある場合は実用的ではありません。特に、その正確な数は通常、事前にわからないためです。幸い、csplit
「前のパターンを可能な限り繰り返す」という意味の特別なパターンがあります。 正規表現のスター数量詞を連想させる構文にもかかわらず、これはKleene plusの概念に近いものです。これは、すでにあるセパレーターを繰り返すために使用されるためです。 一度一致した:
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
そして今回、ついにツイートコレクションを個別に分割しました。ただし、csplip
そのような他の素晴らしい「特別な」パターンがありますか?ええと、それらを「特別」と呼べるかどうかはわかりませんが、間違いなくcsplit
パターンをもっと理解する。
前のセクションで、バインドされていない繰り返しに「{*}」数量詞を使用する方法を見てきました。ただし、星を数字に置き換えることで、正確な繰り返し回数をリクエストできます。
sh$ csplit tweets.yaml \
> --prefix='tweet.' --suffix-format='%03d.yaml' \
> --elide-empty-files \
> /----/ '{6}'
170
250
190
208
140
216
9672
それは興味深いコーナーケースにつながります。繰り返しの数がデータファイル内の実際の区切り文字の数を超えた場合、何が追加されますか?さて、例でそれを見てみましょう:
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
興味深いことに、csplit
だけではありません エラーが報告されましたが、すべても削除されました プロセス中に作成されたチャンクファイル。私の言葉遣いに特に注意してください:それは削除されました 彼ら。つまり、csplit
のときに、ファイルが作成されたということです。 エラーが発生したため、それらを削除しました。つまり、名前がチャンクファイルのように見えるファイルが既にある場合は、削除されます:
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
上記の例では、tweet.002.yaml
手動で作成したファイルは上書きされ、csplit
によって削除されました 。
--keep-files
を使用してその動作を変更できます オプション。その名前が示すように、エラーが発生した後にcsplitが作成したチャンクは削除されません:
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
その場合、エラーにもかかわらず、csplit
に注意してください。 データを破棄しませんでした:
sh$ diff -s tweets.yaml <(cat tweet.*)
Files tweets.yaml and /dev/fd/63 are identical
しかし、ファイルに破棄したいデータがある場合はどうなりますか?ええと、csplit
%regex%
を使用したサポートには制限があります パターン。
csplitでデータをスキップする
パーセント記号を使用する場合(%
)スラッシュの代わりに正規表現区切り文字として(/
)、csplit
スキップ 正規表現に一致する最初の行までのデータ(ただし、含まない)。これは、特に入力ファイルの最初または最後で、一部のレコードを無視すると便利な場合があります。
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
csplitでファイルを分割する際のオフセットの使用
正規表現を使用する場合(/…/
または%…%
)正の値(+N
)を指定できます )または負(-N
)パターンの最後でオフセットされるため、csplit
ファイルを分割しますN 一致する行の後または前の行。すべての場合において、パターンは endを指定することを忘れないでください チャンクの:
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
----
正規表現を使用してファイルを分割する方法については、すでに説明しました。その場合、csplit
ファイルを最初の行で分割します一致 その正規表現。ただし、分割線は、これから説明するように、行番号で識別することもできます。
YAMLに切り替える前は、スケジュールされたツイートをフラットファイルに保存していました。
そのファイルでは、ツイートは2行で構成されていました。 1つはオプションの繰り返しを含み、もう1つはツイートのテキストを含み、改行は\nに置き換えられます。もう一度、そのサンプルファイルはオンラインで入手できます。
その「固定サイズ」フォーマットでもcsplit
を使用できました 個々のツイートを独自のファイルに入れるには:
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
上記の例は理解しやすいようですが、ここには2つの落とし穴があります。まず、2
csplit
への引数として与えられます は行です番号 、行ではありません count 。ただし、私が行ったように繰り返しを使用する場合、最初の一致の後、csplit
その番号を行として使用しますcount 。はっきりしない場合は、次の3つのコマンドの出力を比較してみましょう。
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
私は、最初の落とし穴にいくらか関連する2番目の落とし穴について言及しました。 tweets.txt
の一番上にある空の行に気づいたかもしれません。 ファイル?そのtweet.000.txt
につながります 改行文字のみを含むチャンク。残念ながら、その例では繰り返しのために必要でした。2行が必要なことを忘れないでください。 チャンク。つまり、2
繰り返しの前に必須です。しかし、それは最初のも意味します チャンクはで壊れますが、含まれていません 、2行目。つまり、最初のチャンクには1行が含まれています。他のすべてのものには2行が含まれます。コメント欄で意見を共有できるかもしれませんが、私としては、これは残念なデザインの選択だったと思います。
空でない最初の行に直接スキップすることで、この問題を軽減できます。
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
stdinからの読み取り
もちろん、ほとんどのコマンドラインツールと同様に、csplit
標準入力から入力データを読み取ることができます。その場合、-
を指定する必要があります 入力ファイル名として:
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
今日お見せしたかったのはこれだけです。将来的には、csplitを使用してLinuxでファイルを分割することを願っています。この記事を楽しんだら、お気に入りのソーシャルネットワークで共有して気に入ってもらうことを忘れないでください!