根据列将大型 csv 文件拆分为多个文件
Posted
技术标签:
【中文标题】根据列将大型 csv 文件拆分为多个文件【英文标题】:Split large csv file into multiple files based on column(s) 【发布时间】:2022-01-16 02:21:53 【问题描述】:我想知道在任何程序(awk/perl/python)中将 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...
我目前可以在小文件(比如 30 列)上使用 awk 执行此操作,如下所示:
awk -F, 'BEGINOFS=","; for (i=1; i < NF; i++) print $1, $(i+1) > i ".csv"' large_file.csv
以上对于大文件需要很长时间,我想知道是否有更快、更有效的方法来做同样的事情。
提前致谢。
【问题讨论】:
所以您需要编写大约 10,000 个文件?原始 CSV 文件中有多少行? 你必须看看你的操作系统是否可以处理那么多打开的文件句柄。 @G4143 虽然这对于解决方案很重要,但不必同时打开它们 【参考方案1】:这里的主要障碍是编写这么多文件。
这是一种方法
use warnings;
use strict;
use feature 'say';
my $file = shift // die "Usage: $0 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
文件句柄)可能会带来问题。如果是这种情况,那么最简单的方法是首先组装所有数据,然后一次打开并写入一个文件
use warnings;
use strict;
use feature 'say';
my $file = shift // die "Usage: $0 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 文件是否可以保存在内存中。
【讨论】:
【参考方案2】:用你的展示样本,尝试;请尝试关注awk
代码。由于您同时打开文件,因此可能会因臭名昭著的“打开的文件过多错误”而失败,因此为避免将所有值放入数组中,并在此 awk
代码的 END
块中一一打印它们,我将关闭他们尽快将所有内容打印到输出文件。
awk '
BEGIN FS=OFS=","
for(i=1;i<NF;i++)
value[i]=(value[i]?value[i] ORS:"") ($1 OFS $(i+1))
END
for(i=1;i<=NF;i++)
outFile=i".csv"
print value[i] > (outFile)
close(outFile)
' large_file.csv
【讨论】:
当我尝试这个时,文件 1.csv 出来是空的 - 我错过了什么 @user10101904,我已经对答案进行了编辑,请你现在检查一下,让我知道它是怎么回事,干杯。【参考方案3】:我需要相同的功能并用 bash 编写。 不确定它是否会比ravindersingh13 的答案更快,但我希望它会对某人有所帮助。
实际版本:https://github.com/pgrabarczyk/csv-file-splitter
#!/usr/bin/env bash
set -eu
SOURCE_CSV_PATH="$1"
LINES_PER_FILE="$2"
DEST_PREFIX_NAME="$3"
DEBUG="$4:-0"
split_files()
local source_csv_path="$1"
local lines_per_file="$2"
local dest_prefix_name="$3"
local debug="$4"
_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 $1')
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="$1"
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
【讨论】:
【参考方案4】:尝试使用模块 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);
【讨论】:
以上是关于根据列将大型 csv 文件拆分为多个文件的主要内容,如果未能解决你的问题,请参考以下文章