GNU/Linux >> Znalost Linux >  >> Linux

csplit:Lepší způsob, jak rozdělit soubor v Linuxu na základě jeho obsahu

Pokud jde o rozdělení textového souboru do více souborů v Linuxu, většina lidí používá příkaz split. Na příkazu split není nic špatného, ​​kromě toho, že se při rozdělování souborů spoléhá na velikost bajtu nebo velikost řádku.

To není vhodné v situacích, kdy potřebujete rozdělit soubory na základě jejich obsahu, nikoli velikosti. Dovolte mi uvést příklad.

Své naplánované tweety spravuji pomocí souborů YAML. Typický soubor tweetu obsahuje několik tweetů oddělených čtyřmi pomlčkami:

  ----
    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 :
[...]

Když je importuji do svého systému, musím zapsat každý tweet do vlastního souboru. Dělám to, abych se vyhnul registraci duplicitních tweetů.

Jak ale rozdělit soubor na několik částí na základě jeho obsahu? No, pravděpodobně můžete získat něco přesvědčivého pomocí příkazů awk:

  sh$ awk < tweets.yaml '
  >     /----/ { OUTPUT="tweet." (N++) ".yaml" }
  >     { print > OUTPUT }
  > '

Navzdory relativní jednoduchosti však takové řešení není příliš robustní:například jsem správně neuzavřel různé výstupní soubory, takže to může velmi dobře dosáhnout limitu otevřených souborů. Nebo co když jsem zapomněl oddělovač před úplně prvním tweetem souboru? Samozřejmě, že to vše lze zvládnout a opravit ve skriptu AWK, na úkor jeho komplexnosti. Ale proč se s tím obtěžovat, když máme csplit nástroj k provedení tohoto úkolu?

Použití csplit k rozdělení souborů v Linuxu

csplit nástroj je bratrancem split nástroj, který lze použít k rozdělení souboru na pevnou velikost kousky. Ale csplit bude identifikovat hranice bloků na základě obsahu souboru, spíše než pomocí počtu bajtů.

V tomto tutoriálu předvedu použití příkazu csplit a také vysvětlím výstup tohoto příkazu.

Pokud tedy například chci rozdělit svůj tweetový soubor na základě ---- oddělovač, mohl bych napsat:

  sh$ csplit tweets.yaml /----/
  0
  10846

Možná jste uhodli csplit nástroj použil k identifikaci oddělovače regulární výraz uvedený na příkazovém řádku. A co by mohlo být těch 0 a 10983 výsledek zobrazen na standardním výstupu? Mají velikost v bajtech každého vytvořeného kusu dat.

  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

Počkej chvíli! Kde jsou tyto xx00 a xx01 názvy souborů pocházejí? A proč csplit rozdělit soubor na pouze dva bloky ? A proč má první datový blok délku nula bajtů ?

Odpověď na první otázku je jednoduchá:xxNN (nebo formálněji xx%02d ) je výchozí formát souboru používaný csplit . Ale můžete to změnit pomocí --suffix-format a --prefix možnosti. Mohl bych například změnit formát na něco smysluplnějšího pro mé potřeby:

  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

Předpona je prostý řetězec, ale přípona je formátovací řetězec, jaký používá standardní knihovna C printf funkce. Většina znaků ve formátu bude použita doslovně, s výjimkou specifikací převodu, které jsou uvedeny znakem procenta (% ) a který končí specifikátorem konverze (zde d ). Mezi tím může formát obsahovat také různé příznaky a možnosti. V mém příkladu %03d specifikace konverze znamená:

  • zobrazit číslo bloku jako celé desítkové číslo (d ),
  • v poli o šířce tří znaků (3 ),
  • nakonec doplněno vlevo nulami (0 ).

Ale to se netýká ostatních výslechů, které jsem měl výše:proč máme jen dva chunks, jeden z nich obsahuje nulu bajtů? Možná jste již sami našli odpověď na tuto otázku:můj datový soubor začíná ---- na svém prvním řádku. Takže csplit považoval to za oddělovač, a protože před tímto řádkem nebyla žádná data, vytvořil prázdný první blok. Vytváření souborů s nulovou délkou bajtů můžeme zakázat pomocí --elide-empty-files možnost:

  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:žádné další prázdné soubory. Ale v jistém smyslu je teď výsledek nejhorší, protože csplit rozdělit soubor pouze na jeden kus. Tomu sotva můžeme říkat „rozdělení“ souboru, že?

Vysvětlení tohoto překvapivého výsledku je csplit ne vůbec předpokládejme, že každý sklíčidlo by mělo být rozděleno na základě stejného oddělovač. Vlastně csplit vyžaduje, abyste poskytli každý použitý oddělovač. I když je to několikrát stejné:

  sh$ csplit tweets.yaml \
  >     --prefix='tweet.' --suffix-format='%03d.yaml' \
  >     --elide-empty-files \
  >     /----/ /----/ /----/
  170
  250
  10426

Na příkazový řádek jsem vložil tři (identické) oddělovače. Takže csplit identifikoval konec prvního bloku na základě prvního oddělovače. Vede k bloku délky nula bajtů, který byl elidován. Druhý blok byl oddělen dalším řádkem odpovídajícím /----/ . To vede k bloku o velikosti 170 bajtů. Nakonec byl na základě třetího oddělovače identifikován třetí blok o délce 250 bajtů. Zbývající data, 10426 bajtů, byla vložena do posledního bloku.

  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

Je zřejmé, že by nebylo praktické, kdybychom museli na příkazovém řádku poskytnout tolik oddělovačů, kolik je kusů v datovém souboru. Zejména proto, že přesné číslo obvykle není předem známo. Naštěstí csplit má speciální vzor s významem „co nejvíce opakujte předchozí vzor.“ Navzdory své syntaxi připomínající hvězdný kvantifikátor v regulárním výrazu se tento koncept blíží konceptu Kleene plus, protože se používá k opakování oddělovače, který již se shoduje jednou:

  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

A tentokrát jsem konečně svou sbírku tweetů rozdělil na jednotlivé části. Nicméně csplip máte nějaké další pěkné "speciální" vzory, jako je tento? No, nevím, jestli je můžeme nazvat „speciálními“, ale rozhodně csplit pochopit více vzorců.

Další vzory csplit

Právě jsme viděli v předchozí části, jak použít kvantifikátor „{*}“ pro nevázaná opakování. Nahrazením hvězdičky číslem si však můžete vyžádat přesný počet opakování:

  sh$ csplit tweets.yaml \
  >     --prefix='tweet.' --suffix-format='%03d.yaml' \
  >     --elide-empty-files \
  >     /----/ '{6}'
  170
  250
  190
  208
  140
  216
  9672

To vede k zajímavému rohovému případu. Co by se přidalo, kdyby počet opakování přesáhl počet skutečných oddělovačů v datovém souboru? Podívejme se na to na příkladu:

  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

Zajímavé je, že nejen csplit nahlásil chybu, ale také odstranil vše soubory bloků vytvořené během procesu. Věnujte zvláštní pozornost mé formulaci:odstraněno jim. To znamená, že soubory byly vytvořeny při csplit došlo k chybě, smazal je. Jinými slovy, pokud již máte soubor, jehož název vypadá jako blokový soubor, bude odstraněn:

  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

Ve výše uvedeném příkladu tweet.002.yaml soubor, který jsme ručně vytvořili, byl přepsán a poté odstraněn pomocí csplit .

Toto chování můžete změnit pomocí --keep-files volba. Jak již název napovídá, neodstraní části csplit vytvořené po zjištění chyby:

  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

Všimněte si v tom případě a navzdory chybě csplit nezahodila žádná data:

  sh$ diff -s tweets.yaml <(cat tweet.*)
  Files tweets.yaml and /dev/fd/63 are identical

Ale co když jsou v souboru nějaká data, která chci zrušit? No, csplit má pro to určitou omezenou podporu pomocí %regex% vzor.

Přeskočení dat v csplit

Při použití znaku procenta (% ) jako oddělovač regulárního výrazu namísto lomítka (/ ), csplit přeskočí data až do (ale ne včetně) prvního řádku odpovídající regulárnímu výrazu. To může být užitečné pro ignorování některých záznamů, zejména na začátku nebo na konci vstupního souboru:

  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

Použití offsetů při rozdělování souborů pomocí csplit

Při použití regulárních výrazů (buď /…​/ nebo %…​% ) můžete zadat kladné číslo (+N ) nebo záporné (-N ) offset na konci vzoru, takže csplit rozdělí soubor N řádky za nebo před odpovídající řádkou. Pamatujte, že ve všech případech vzor určuje konec z části:

  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
  ----

Rozdělit podle čísla řádku

Již jsme viděli, jak můžeme použít regulární výraz k rozdělení souborů. V takovém případě csplit rozdělí soubor na prvním řádku odpovídající ten regulární výraz. Ale můžete také identifikovat rozdělenou čáru podle jejího čísla řádku, jak to nyní uvidíme.

Před přechodem na YAML jsem si své naplánované tweety ukládal do plochého souboru.

V tomto souboru byl tweet vytvořen ze dvou řádků. Jeden obsahuje volitelné opakování a druhý obsahuje text tweetu s novými řádky nahrazenými \n. Tento ukázkový soubor je opět dostupný online.

S tímto formátem „pevné velikosti“ bylo také možné použít csplit pro vložení každého jednotlivého tweetu do vlastního souboru:

  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

Výše uvedený příklad se zdá být snadno pochopitelný, ale jsou zde dvě úskalí. Nejprve 2 zadaný jako argument pro csplit je číslo řádku , nikoli počet řádků . Když však používám opakování jako já, po první shodě csplit použije toto číslo jako počet řádku . Pokud to není jasné, nechám vás porovnat výstup tří následujících příkazů:

  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

Zmínil jsem druhé úskalí, trochu související s tím prvním. Možná jste si všimli prázdného řádku úplně nahoře v tweets.txt soubor? Vede k tomuto tweet.000.txt blok, který obsahuje pouze znak nového řádku. Bohužel to bylo v tomto příkladu vyžadováno kvůli opakování:nezapomeňte, že chci dva řádky kousky. Takže 2 je povinné před opakováním. To ale také znamená první kus se zlomí při, ale bez , řádek dva. Jinými slovy, první blok obsahuje jeden řádek. Všechny ostatní budou obsahovat 2 řádky. Možná byste se mohli podělit o svůj názor v sekci komentářů, ale podle sebe si myslím, že to byla nešťastná volba designu.

Tento problém můžete zmírnit přeskočením přímo na první neprázdný řádek:

  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

Čtení z stdin

Samozřejmě, jako většina nástrojů příkazového řádku, csplit může číst vstupní data ze svého standardního vstupu. V takovém případě musíte zadat - jako vstupní název souboru:

  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

A to je tak všechno, co jsem vám dnes chtěl ukázat. Doufám, že v budoucnu budete používat csplit k rozdělení souborů v Linuxu. Pokud se vám tento článek líbil a nezapomeňte jej sdílet a lajkovat na své oblíbené sociální síti!


Linux
  1. Úvod do Linuxu KVM (kernel Based Virtualization) a jeho výhod

  2. Jak rozdělit iso nebo soubor pomocí příkazu „split“ v Linuxu

  3. Nejúčinnější způsob kopírování souboru v Linuxu

  1. Snadný způsob, jak skrýt soubory a adresáře v Linuxu

  2. Jak mohu vytvořit vícedílný soubor tar v Linuxu?

  3. Správce/instalátor balíčků založený na Git pro Linux

  1. 5 způsobů, jak vyprázdnit nebo odstranit obsah velkého souboru v systému Linux

  2. Linux – všechno je soubor?

  3. 9 Užitečné příklady příkazu Split v Linuxu