Étant donné: Un gros fichier de données texte (par exemple au format CSV) avec une première ligne `` spéciale '' (par exemple, les noms de champ).
Wanted: Un équivalent des coreutils split -l
, mais avec l'exigence supplémentaire que la ligne d'en-tête du fichier d'origine apparaisse au début de chacune des pièces résultantes.
Je suppose que la concoction de split
et head
fera l'affaire?
C'est le script de robhruska nettoyé un peu:
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
J'ai supprimé wc
, cut
, ls
et echo
aux endroits où ils ne sont pas nécessaires. J'ai changé certains des noms de fichiers pour les rendre un peu plus significatifs. Je l'ai réparti sur plusieurs lignes uniquement pour en faciliter la lecture.
Si vous voulez avoir de la fantaisie, vous pouvez utiliser mktemp
ou tempfile
pour créer un nom de fichier temporaire au lieu d'utiliser un codé en dur.
Modifier
En utilisant GNU split
, il est possible de faire ceci:
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_
Découpé pour plus de lisibilité:
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_
Quand --filter
est spécifié, split
exécute la commande (une fonction dans ce cas, qui doit être exportée) pour chaque fichier de sortie et définit la variable FILE
, dans l'environnement de la commande, sur le nom de fichier.
Un script ou une fonction de filtrage peut effectuer toute manipulation qu'il souhaite sur le contenu de sortie ou même sur le nom de fichier. Un exemple de ce dernier pourrait être de produire un nom de fichier fixe dans un répertoire de variables: > "$FILE/data.dat"
par exemple.
Vous pouvez utiliser la nouvelle fonctionnalité --filter dans GNU split coreutils> = 8.13 (2011):
tail -n +2 FILE.in |
split -l 50 - --filter='sh -c "{ head -n1 FILE.in; cat; } > $FILE"'
Vous pouvez utiliser [mg] awk:
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 est le nombre de lignes de chaque tranche. Il ne nécessite pas de fichiers temporaires et peut être mis sur une seule ligne.
Je suis novice en matière de Bash-fu, mais j'ai pu concocter cette monstruosité à deux commandes. Je suis sûr qu'il existe des solutions plus élégantes.
$> 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
Cela suppose que votre fichier d'entrée est file.txt
, vous n'utilisez pas l'argument prefix
pour split
, et vous travaillez dans un répertoire qui ne contient aucun autre fichier commençant par la valeur par défaut de split
xa*
format de sortie. Remplacez également le "4" par la taille de ligne de séparation souhaitée.
Cela divisera le grand csv en morceaux de 999 lignes, avec l'en-tête en haut de chacun
cat bigFile.csv | parallel --header : --pipe -N999 'cat >file_{#}.csv'
Basé sur la réponse d'Ole Tange. (concernant la réponse d'Ole: vous ne pouvez pas utiliser le nombre de lignes avec pipepart)
Il s'agit d'une version plus robuste du script de Denis Williamson . Le script crée beaucoup de fichiers temporaires, et il serait dommage qu'ils soient laissés traîner si l'exécution était incomplète. Ajoutons donc le piégeage du signal (voir http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html puis http://tldp.org/ LDP/abs/html/debugging.html ) et supprimez nos fichiers temporaires; c'est de toute façon une bonne pratique.
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
Remplacez "13" par le code retour que vous souhaitez. Oh, et vous devriez probablement utiliser mktemp de toute façon (comme certains l'ont déjà suggéré), alors allez-y et supprimez 'tmp_file "de la rm dans la ligne de piège. Consultez la page de manuel du signal pour plus de signaux à intercepter.
Inspiré par le commentaire de @ Arkady sur un one-liner.
split
n'affiche pas le nom du fichier, mais l'option --additional-suffix
nous permet de contrôler facilement à quoi s'attendrerm $part
(suppose aucun fichier avec le même suffixe)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
Preuve:
-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
et bien sûr head -2 *foo
pour voir l'en-tête est ajouté.
Je ne suis jamais sûr des règles de copie de scripts directement à partir des sites d'autres personnes, mais Geekology a un bon script pour faire ce que vous voulez, avec quelques commentaires confirmant que cela fonctionne. Assurez-vous de faire tail
-n
+2
comme indiqué dans un commentaire vers le bas.
J'ai aimé la version awk de marco, adoptée à partir de celle-ci, une doublure simplifiée où vous pouvez facilement spécifier la fraction fractionnée aussi granulaire que vous le souhaitez:
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
J'ai vraiment aimé les versions de Rob et Dennis, à tel point que je voulais les améliorer.
Voici ma version:
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
Différences:
awk
au lieu de tail
car awk
a de meilleures performanceshead | cat
ligne au lieu de deux lignesVous trouverez ci-dessous un liner 4 qui peut être utilisé pour conserver l'en-tête csv (en utilisant: head, split, find, grep, xargs et sed)
csvheader = `head -1 bigfile.csv` split -d -l10000 bigfile.csv smallfile _ find. | grep smallfile_ | xargs sed -i "1s/^/$ csvheader\n /" sed -i '1d' smallfile_00
Explication:
Utilisez GNU Parallèle:
parallel -a bigfile.csv --header : --pipepart 'cat > {#}'
Si vous devez exécuter une commande sur chacune des parties, alors GNU Parallel peut aussi vous aider à faire cela:
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 {}
Si vous souhaitez diviser en 2 parties par cœur de processeur (par exemple 24 cœurs = 48 parties de taille égale):
parallel --block -2 -a bigfile.csv --header : --pipepart my_program_reading_from_stdin
Si vous souhaitez diviser en blocs de 10 Mo:
parallel --block 10M -a bigfile.csv --header : --pipepart my_program_reading_from_stdin