根据列将大型 csv 文件拆分为多个文件
Split large csv file into multiple files based on column(s)
我想知道在任何程序 (awk/perl/python) 中有一种 fast/efficient 方法可以将 csv 文件(例如 10k 列)拆分为多个小文件,每个文件包含 2 列。我会在 unix 机器上做这个。
#contents of large_file.csv
1,2,3,4,5,6,7,8
a,b,c,d,e,f,g,h
q,w,e,r,t,y,u,i
a,s,d,f,g,h,j,k
z,x,c,v,b,n,m,z
我现在想要多个这样的文件:
# contents of 1.csv
1,2
a,b
q,w
a,s
z,x
# contents of 2.csv
1,3
a,c
q,e
a,d
z,c
# contents of 3.csv
1,4
a,d
q,r
a,f
z,v
and so on...
我目前可以使用 awk 对小文件(例如 30 列)执行此操作,如下所示:
awk -F, 'BEGIN{OFS=",";} {for (i=1; i < NF; i++) print , $(i+1) > i ".csv"}' large_file.csv
以上处理大文件需要很长时间,我想知道是否有更快、更有效的方法来做同样的事情。
提前致谢。
用你的秀样,尝试;请尝试使用 awk
代码。由于您同时打开文件,它可能会因臭名昭著的“打开的文件太多错误”而失败因此,为了避免将所有值都放入一个数组中,并在此 awk
代码的 END
块中一一打印它们我正在尽快关闭它们,所有内容都被打印到输出文件中。
awk '
BEGIN{ FS=OFS="," }
{
for(i=1;i<NF;i++){
value[i]=(value[i]?value[i] ORS:"") ( OFS $(i+1))
}
}
END{
for(i=1;i<=NF;i++){
outFile=i".csv"
print value[i] > (outFile)
close(outFile)
}
}
' large_file.csv
这里的主要问题是写这么多文件。
这是一种方法
use warnings;
use strict;
use feature 'say';
my $file = shift // die "Usage: [=10=] csv-file\n";
my @lines = do { local @ARGV = $file; <> };
chomp @lines;
my @fhs = map {
open my $fh, '>', "f${_}.csv" or die $!;
$fh
}
1 .. scalar( split /,/, $lines[0] );
for (@lines) {
my ($first, @cols) = split /,/;
say {$fhs[$_]} join(',', $first, $cols[$_])
for 0..$#cols;
}
我没有将此与任何其他方法进行比较。首先为每个文件组装数据,然后在一个操作中将其转储到每个文件中可能会有所帮助,但首先让我们知道原始 CSV 文件有多大。
一次打开如此多的输出文件(对于 @fhs
个文件句柄)可能会出现问题。如果是这样那么最简单的方法就是先assemble所有数据,然后一次打开并写入一个文件
use warnings;
use strict;
use feature 'say';
my $file = shift // die "Usage: [=11=] csv-file\n";
open my $fh, '<', $file or die "Can't open $file: $!";
my @data;
while (<$fh>) {
chomp;
my ($first, @cols) = split /,/;
push @{$data[$_]}, join(',', $first, $cols[$_])
for 0..$#cols;
}
for my $i (0..$#data) {
open my $fh, '>', $i+1 . '.csv' or die $!;
say $fh $_ for @{$data[$i]};
}
这取决于整个原始 CSV 文件以及更多文件是否可以保存在内存中。
尝试了使用模块 Text::CSV 的解决方案。
#! /usr/bin/env perl
use warnings;
use strict;
use utf8;
use open qw<:std :encoding(utf-8)>;
use autodie;
use feature qw<say>;
use Text::CSV;
my %hsh = ();
my $csv = Text::CSV->new({ sep_char => ',' });
print "Enter filename: ";
chomp(my $filename = <STDIN>);
open (my $ifile, '<', $filename);
while (<$ifile>) {
chomp;
if ($csv->parse($_)) {
my @fields = $csv->fields();
my $first = shift @fields;
while (my ($i, $v) = each @fields) {
push @{$hsh{($i + 1).".csv"}}, "$first,$v";
}
} else {
die "Line could not be parsed: $_\n";
}
}
close($ifile);
while (my ($k, $v) = each %hsh) {
open(my $ifile, '>', $k);
say {$ifile} $_ for @$v;
close($ifile);
}
exit(0);
我需要相同的功能并将其写在 bash 中。
不确定它是否会比 ravindersingh13 的回答更快,但我希望它能帮助别人。
实际版本:https://github.com/pgrabarczyk/csv-file-splitter
#!/usr/bin/env bash
set -eu
SOURCE_CSV_PATH=""
LINES_PER_FILE=""
DEST_PREFIX_NAME=""
DEBUG="${4:-0}"
split_files() {
local source_csv_path=""
local lines_per_file=""
local dest_prefix_name=""
local debug=""
_print_log "source_csv_path: ${source_csv_path}"
local dest_prefix_path="$(pwd)/output/${dest_prefix_name}"
_print_log "dest_prefix_path: ${dest_prefix_path}"
local headline=$(awk "NR==1" "${source_csv_path}")
local file_no=0
mkdir -p "$(dirname ${dest_prefix_path})"
local lines_in_files=$(wc -l "${source_csv_path}" | awk '{print }')
local files_to_create=$(((lines_in_files-1)/lines_per_file))
_print_log "There is ${lines_in_files} lines in file. I will create ${files_to_create} files per ${lines_per_file} (Last file may have less)"
_print_log "Start processing."
for (( start_line=1; start_line<=lines_in_files; )); do
last_line=$((start_line+lines_per_file))
file_no=$((file_no+1))
local file_path="${dest_prefix_path}$(printf "%06d" ${file_no}).csv"
if [ $debug -eq 1 ]; then
_print_log "Creating file ${file_path} with lines [${start_line};${last_line}]"
fi
echo "${headline}" > "${file_path}"
awk "NR>${start_line} && NR<=${last_line}" "${source_csv_path}" >> "${file_path}"
start_line=$last_line
done
_print_log "Done."
}
_print_log() {
local log_message=""
local date_time=$(date "+%Y-%m-%d %H:%M:%S.%3N")
printf "%s - %s\n" "${date_time}" "${log_message}" >&2
}
split_files "${SOURCE_CSV_PATH}" "${LINES_PER_FILE}" "${DEST_PREFIX_NAME}" "${DEBUG}"
执行:
bash csv-file-splitter.sh "sample.csv" 3 "result_" 1
我想知道在任何程序 (awk/perl/python) 中有一种 fast/efficient 方法可以将 csv 文件(例如 10k 列)拆分为多个小文件,每个文件包含 2 列。我会在 unix 机器上做这个。
#contents of large_file.csv
1,2,3,4,5,6,7,8
a,b,c,d,e,f,g,h
q,w,e,r,t,y,u,i
a,s,d,f,g,h,j,k
z,x,c,v,b,n,m,z
我现在想要多个这样的文件:
# contents of 1.csv
1,2
a,b
q,w
a,s
z,x
# contents of 2.csv
1,3
a,c
q,e
a,d
z,c
# contents of 3.csv
1,4
a,d
q,r
a,f
z,v
and so on...
我目前可以使用 awk 对小文件(例如 30 列)执行此操作,如下所示:
awk -F, 'BEGIN{OFS=",";} {for (i=1; i < NF; i++) print , $(i+1) > i ".csv"}' large_file.csv
以上处理大文件需要很长时间,我想知道是否有更快、更有效的方法来做同样的事情。
提前致谢。
用你的秀样,尝试;请尝试使用 awk
代码。由于您同时打开文件,它可能会因臭名昭著的“打开的文件太多错误”而失败因此,为了避免将所有值都放入一个数组中,并在此 awk
代码的 END
块中一一打印它们我正在尽快关闭它们,所有内容都被打印到输出文件中。
awk '
BEGIN{ FS=OFS="," }
{
for(i=1;i<NF;i++){
value[i]=(value[i]?value[i] ORS:"") ( OFS $(i+1))
}
}
END{
for(i=1;i<=NF;i++){
outFile=i".csv"
print value[i] > (outFile)
close(outFile)
}
}
' large_file.csv
这里的主要问题是写这么多文件。
这是一种方法
use warnings;
use strict;
use feature 'say';
my $file = shift // die "Usage: [=10=] csv-file\n";
my @lines = do { local @ARGV = $file; <> };
chomp @lines;
my @fhs = map {
open my $fh, '>', "f${_}.csv" or die $!;
$fh
}
1 .. scalar( split /,/, $lines[0] );
for (@lines) {
my ($first, @cols) = split /,/;
say {$fhs[$_]} join(',', $first, $cols[$_])
for 0..$#cols;
}
我没有将此与任何其他方法进行比较。首先为每个文件组装数据,然后在一个操作中将其转储到每个文件中可能会有所帮助,但首先让我们知道原始 CSV 文件有多大。
一次打开如此多的输出文件(对于 @fhs
个文件句柄)可能会出现问题。如果是这样那么最简单的方法就是先assemble所有数据,然后一次打开并写入一个文件
use warnings;
use strict;
use feature 'say';
my $file = shift // die "Usage: [=11=] csv-file\n";
open my $fh, '<', $file or die "Can't open $file: $!";
my @data;
while (<$fh>) {
chomp;
my ($first, @cols) = split /,/;
push @{$data[$_]}, join(',', $first, $cols[$_])
for 0..$#cols;
}
for my $i (0..$#data) {
open my $fh, '>', $i+1 . '.csv' or die $!;
say $fh $_ for @{$data[$i]};
}
这取决于整个原始 CSV 文件以及更多文件是否可以保存在内存中。
尝试了使用模块 Text::CSV 的解决方案。
#! /usr/bin/env perl
use warnings;
use strict;
use utf8;
use open qw<:std :encoding(utf-8)>;
use autodie;
use feature qw<say>;
use Text::CSV;
my %hsh = ();
my $csv = Text::CSV->new({ sep_char => ',' });
print "Enter filename: ";
chomp(my $filename = <STDIN>);
open (my $ifile, '<', $filename);
while (<$ifile>) {
chomp;
if ($csv->parse($_)) {
my @fields = $csv->fields();
my $first = shift @fields;
while (my ($i, $v) = each @fields) {
push @{$hsh{($i + 1).".csv"}}, "$first,$v";
}
} else {
die "Line could not be parsed: $_\n";
}
}
close($ifile);
while (my ($k, $v) = each %hsh) {
open(my $ifile, '>', $k);
say {$ifile} $_ for @$v;
close($ifile);
}
exit(0);
我需要相同的功能并将其写在 bash 中。 不确定它是否会比 ravindersingh13 的回答更快,但我希望它能帮助别人。
实际版本:https://github.com/pgrabarczyk/csv-file-splitter
#!/usr/bin/env bash
set -eu
SOURCE_CSV_PATH=""
LINES_PER_FILE=""
DEST_PREFIX_NAME=""
DEBUG="${4:-0}"
split_files() {
local source_csv_path=""
local lines_per_file=""
local dest_prefix_name=""
local debug=""
_print_log "source_csv_path: ${source_csv_path}"
local dest_prefix_path="$(pwd)/output/${dest_prefix_name}"
_print_log "dest_prefix_path: ${dest_prefix_path}"
local headline=$(awk "NR==1" "${source_csv_path}")
local file_no=0
mkdir -p "$(dirname ${dest_prefix_path})"
local lines_in_files=$(wc -l "${source_csv_path}" | awk '{print }')
local files_to_create=$(((lines_in_files-1)/lines_per_file))
_print_log "There is ${lines_in_files} lines in file. I will create ${files_to_create} files per ${lines_per_file} (Last file may have less)"
_print_log "Start processing."
for (( start_line=1; start_line<=lines_in_files; )); do
last_line=$((start_line+lines_per_file))
file_no=$((file_no+1))
local file_path="${dest_prefix_path}$(printf "%06d" ${file_no}).csv"
if [ $debug -eq 1 ]; then
_print_log "Creating file ${file_path} with lines [${start_line};${last_line}]"
fi
echo "${headline}" > "${file_path}"
awk "NR>${start_line} && NR<=${last_line}" "${source_csv_path}" >> "${file_path}"
start_line=$last_line
done
_print_log "Done."
}
_print_log() {
local log_message=""
local date_time=$(date "+%Y-%m-%d %H:%M:%S.%3N")
printf "%s - %s\n" "${date_time}" "${log_message}" >&2
}
split_files "${SOURCE_CSV_PATH}" "${LINES_PER_FILE}" "${DEST_PREFIX_NAME}" "${DEBUG}"
执行:
bash csv-file-splitter.sh "sample.csv" 3 "result_" 1