GNU/Linux >> Linux の 問題 >  >> Linux

csplit:コンテンツに基づいてLinuxでファイルを分割するためのより良い方法

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 パターンをもっと理解する。

その他の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でファイルを分割することを願っています。この記事を楽しんだら、お気に入りのソーシャルネットワークで共有して気に入ってもらうことを忘れないでください!


Linux
  1. Linux KVM (カーネルベースの仮想化) とその利点の紹介

  2. Linuxでタブ区切りファイルをcsvに変換する最速の方法

  3. Linux でファイルをコピーする最も効率的な方法

  1. Linuxで大きなファイルコンテンツを空または削除する5つの方法

  2. Linuxでファイルとディレクトリを隠す簡単な方法

  3. 行に基づいて単一のファイルを複数のファイルに分割する方法

  1. Linux –すべてがファイルですか?

  2. LinuxでのSplitコマンドの9つの便利な例

  3. Linux で「split」コマンドを使用して iso またはファイルを分割する方法