Hoe een bestand splitsen en de eerste regel in elk van de stukken behouden?

Gegeven:één groot tekstbestand (bijv. CSV-indeling) met een ‘speciale’ eerste regel (bijv. veldnamen).

Gezocht:een equivalent van de opdracht coreutils split -l, maar met de aanvullende vereiste dat de kopregel van het originele bestand aan het begin van elk van de resulterende stukken.

Ik vermoed dat een verzinsel van spliten headhet zal doen?


Antwoord 1, autoriteit 100%

Dit is het script van robhruskawat opgeschoond:

tail -n +2 file.txt | split -l 4 - split_
for file in split_*
do
    head -n 1 file.txt > tmp_file
    cat "$file" >> tmp_file
    mv -f tmp_file "$file"
done

Ik heb wc, cut, lsen echoverwijderd op de plaatsen waar ze niet nodig zijn. Ik heb enkele bestandsnamen gewijzigd om ze een beetje betekenisvoller te maken. Ik heb het in meerdere regels verdeeld om het gemakkelijker leesbaar te maken.

Als je zin hebt, kun je mktempof tempfilegebruiken om een ​​tijdelijke bestandsnaam te maken in plaats van een hardgecodeerde naam te gebruiken.

Bewerken

Met GNU splitis het mogelijk om dit te doen:

split_filter () { { head -n 1 file.txt; cat; } > "$FILE"; }; export -f split_filter; tail -n +2 file.txt | split --lines=4 --filter=split_filter - split_

Uitgesplitst voor leesbaarheid:

split_filter () { { head -n 1 file.txt; cat; } > "$FILE"; }
export -f split_filter
tail -n +2 file.txt | split --lines=4 --filter=split_filter - split_

Als --filteris opgegeven, voert splitde opdracht uit (in dit geval een functie die moet worden geëxporteerd) voor elk uitvoerbestand en stelt de variabele FILE, in de opdrachtomgeving, naar de bestandsnaam.

Een filterscript of -functie kan elke gewenste manipulatie doen aan de uitvoerinhoud of zelfs de bestandsnaam. Een voorbeeld van het laatste zou kunnen zijn om uit te voeren naar een vaste bestandsnaam in een variabele directory: > "$FILE/data.dat"bijvoorbeeld.


Antwoord 2, autoriteit 34%

Deze one-liner splitst de grote csv in stukjes van 999 records, waarbij de koprij bovenaan elke record behouden blijft (dus 999 records + 1 kop = 1000 rijen)

cat bigFile.csv | parallel --header : --pipe -N999 'cat >file_{#}.csv'

Gebaseerd op het antwoord van Ole Tange.
(over het antwoord van Ole: je kunt het aantal regels niet gebruiken met pipepart)

Bekijk de opmerkingen voor enkele tips voor het installeren van parallel


Antwoord 3, autoriteit 22%

Je zou de nieuwe –filter functionaliteit in GNU coreutils split >= 8.13 (2011) kunnen gebruiken:

tail -n +2 FILE.in | split -l 50 - --filter='sh -c "{ head -n1 FILE.in; cat; } > $FILE"'

Antwoord 4, autoriteit 21%

U kunt [mg]awk:

. gebruiken

awk 'NR==1{
        header=$0; 
        count=1; 
        print header > "x_" count; 
        next 
     } 
     !( (NR-1) % 100){
        count++; 
        print header > "x_" count;
     } 
     {
        print $0 > "x_" count
     }' file

100 is het aantal regels van elk segment.
Het vereist geen tijdelijke bestanden en kan op een enkele regel worden geplaatst.


Antwoord 5, autoriteit 12%

Ik ben een beginneling als het gaat om Bash-fu, maar ik heb dit twee-commando-monster kunnen verzinnen. Ik weet zeker dat er elegantere oplossingen zijn.

$> tail -n +2 file.txt | split -l 4
$> for file in `ls xa*`; do echo "`head -1 file.txt`" > tmp; cat $file >> tmp; mv -f tmp $file; done

Dit veronderstelt dat uw invoerbestand file.txtis, dat u het argument prefixniet gebruikt om splitte gebruiken, en dat u werken in een map die geen andere bestanden heeft die beginnen met split‘s standaard xa*uitvoerformaat. Vervang ook de ‘4’ door de gewenste grootte van de gesplitste lijn.


Antwoord 6, autoriteit 4%

Gebruik GNU Parallel:

parallel -a bigfile.csv --header : --pipepart 'cat > {#}'

Als je een commando moet uitvoeren op elk van de onderdelen, dan kan GNU Parallel je daarbij ook helpen:

parallel -a bigfile.csv --header : --pipepart my_program_reading_from_stdin
parallel -a bigfile.csv --header : --pipepart --fifo my_program_reading_from_fifo {}
parallel -a bigfile.csv --header : --pipepart --cat my_program_reading_from_a_file {}

Als u wilt splitsen in 2 delen per CPU-kern (bijvoorbeeld 24 kernen = 48 gelijke delen):

parallel --block -2 -a bigfile.csv --header : --pipepart my_program_reading_from_stdin

Als je wilt opsplitsen in blokken van 10 MB:

parallel --block 10M -a bigfile.csv --header : --pipepart my_program_reading_from_stdin

Antwoord 7, autoriteit 3%

Dit is een robuustere versie van het script van Denis Williamson. Het script maakt veel tijdelijke bestanden aan en het zou zonde zijn als ze zouden blijven rondslingeren als de run onvolledig was. Laten we dus signaaltrapping toevoegen (zie http://tldp.org/ LDP/Bash-Beginners-Guide/html/sect_12_02.htmlen dan http ://tldp.org/LDP/abs/html/debugging.html) en verwijder onze tijdelijke bestanden; dit is sowieso een best practice.

trap 'rm split_* tmp_file ; exit 13' SIGINT SIGTERM SIGQUIT 
tail -n +2 file.txt | split -l 4 - split_
for file in split_*
do
    head -n 1 file.txt > tmp_file
    cat $file >> tmp_file
    mv -f tmp_file $file
done

Vervang ’13’ door de gewenste retourcode. Oh, en je zou waarschijnlijk sowieso mktemp moeten gebruiken (zoals sommigen al hebben gesuggereerd), dus ga je gang en verwijder ‘tmp_file’ uit de rm in de trap-regel. Zie de signal man-pagina voor meer signalen om te vangen.


Antwoord 8, autoriteit 3%

Ik vond de awk-versie van marco leuk, hieruit overgenomen een vereenvoudigde one-liner waar je de gesplitste fractie gemakkelijk zo gedetailleerd kunt specificeren als je wilt:

awk 'NR==1{print $0 > FILENAME ".split1";  print $0 > FILENAME ".split2";} NR>1{if (NR % 10 > 5) print $0 >> FILENAME ".split1"; else print $0 >> FILENAME ".split2"}' file

Antwoord 9, autoriteit 3%

Ik vond de versies van Rob en Dennis zo goed dat ik ze wilde verbeteren.

Hier is mijn versie:

in_file=$1
awk '{if (NR!=1) {print}}' $in_file | split -d -a 5 -l 100000 - $in_file"_" # Get all lines except the first, split into 100,000 line chunks
for file in $in_file"_"*
do
    tmp_file=$(mktemp $in_file.XXXXXX) # Create a safer temp file
    head -n 1 $in_file | cat - $file > $tmp_file # Get header from main file, cat that header with split file contents to temp file
    mv -f $tmp_file $file # Overwrite non-header containing file with header-containing file
done

Verschillen:

  1. in_file is het bestandsargument dat u wilt splitsen met behoud van headers
  2. Gebruik awkin plaats van tailomdat awkbetere prestaties levert
  3. opgesplitst in 100.000 regelbestanden in plaats van 4
  4. Gesplitste bestandsnaam wordt ingevoerd als bestandsnaam, aangevuld met een onderstrepingsteken en cijfers (maximaal 99999 – van het “-d -a 5” split-argument)
  5. Gebruik MKTEMP om de tijdelijke bestanden veilig af te handelen
  6. Gebruik enkele head | catLijn in plaats van twee lijnen

Antwoord 10, Autoriteit 3%

Hieronder is een 4 voering die kan worden gebruikt om een ​​BIGFILE.CSV in meerdere kleinere bestanden te splitsen en de CSV-header te behouden. Gebruikt alleen ingebouwde bash-opdrachten (hoofd, split, find, grep, xargs en sed) die op de meeste * nix-systemen zou moeten werken. Moet ook werken op Windows als u MINGW-64 / GIT-bash installeert.

CSVHADEER = `Head -1 bigfile.csv`
Split -d -L10000 bigfile.csv smallfile_
Zoek. | grep smallfile_ | XARGS SED -I "1S / ^ / $ CSVHEADER \ N /"
sed -i '1d' smallfile_00

Lijn op lijnuitleg:

  1. Leg de koptekst vast aan een variabele genaamd CSVHearer
  2. Split de bigfile.csv in een aantal kleinere bestanden met prefix smallfile _
  3. Zoek alle smallfiles en plaats de CSVHADEER in de eerste regel met xargs en sed -i . Merk op dat u SED binnen “Dubbele Quotes” moet gebruiken om variabelen te gebruiken.
  4. Het eerste bestand met de naam SmallFile_00 heeft nu overbodig headers op lijnen 1 en 2 (uit de originele gegevens en van de SED-header-inzetstuk in stap 3). We kunnen de overtollige koptekst verwijderen met SED -I ‘1D’ -opdracht.

Antwoord 11

Geïnspireerd door @ arkady’s opmerking op een one-liner.

  • MyFile-variabele om simpelweg de boilerplaat te verminderen
  • splittoont geen bestandsnaam, maar de --additional-suffixOptie stelt ons in staat om gemakkelijk te regelen wat u kunt verwachten
  • Verwijdering van tussenliggende bestanden via rm $part(veronderstelt geen bestanden met hetzelfde achtervoegsel)

MYFILE=mycsv.csv && for part in $(split -n4 --additional-suffix=foo $MYFILE; ls *foo); do cat <(head -n1 $MYFILE) $part > $MYFILE.$part; rm $part; done

Bewijs:

-rw-rw-r--  1 ec2-user ec2-user  32040108 Jun  1 23:18 mycsv.csv.xaafoo
-rw-rw-r--  1 ec2-user ec2-user  32040108 Jun  1 23:18 mycsv.csv.xabfoo
-rw-rw-r--  1 ec2-user ec2-user  32040108 Jun  1 23:18 mycsv.csv.xacfoo
-rw-rw-r--  1 ec2-user ec2-user  32040110 Jun  1 23:18 mycsv.csv.xadfoo

en natuurlijk head -2 *fooom te zien dat de koptekst wordt toegevoegd.


Antwoord 12

Een eenvoudige maar misschien niet zo elegant: snijd de kop van tevoren af, split het bestand en ga vervolgens opnieuw in de kop op elk bestand met kat, of met welk bestand het ook is.
Dus zoiets als:

  1. head -n1 file.txt & gt; header.txt
  2. Split -l file.txt
  3. Cat header.txt f1.txt

Other episodes