输入文件:
column1;column2;column3
data1a;data2a;data3a
data1b;data2b;data3b
目标:输出带有重新排序列的文件,比如说
column1;column3;column2
...
更新的问题: 使用PowerShell解决此问题的好方法是什么。 我知道存在与CSV相关的cmdlet,但这些都有局限性。 请注意,不需要更改记录的顺序,因此不需要将整个输入/输出文件加载到内存中。
答案 0 :(得分:17)
以下是适用于数百万条记录的解决方案(假设您的数据没有嵌入';')
$reader = [System.IO.File]::OpenText('data1.csv')
$writer = New-Object System.IO.StreamWriter 'data2.csv'
for(;;) {
$line = $reader.ReadLine()
if ($null -eq $line) {
break
}
$data = $line.Split(";")
$writer.WriteLine('{0};{1};{2}', $data[0], $data[2], $data[1])
}
$reader.Close()
$writer.Close()
答案 1 :(得分:15)
Import-CSV C:\Path\To\Original.csv | Select-Object Column1, Column3, Column2 | Export-CSV C:\Path\To\Newfile.csv
答案 2 :(得分:5)
修改:以下基准信息。
我不会使用与Powershell csv相关的cmdlet。我会使用System.IO.StreamReader
或Microsoft.VisualBasic.FileIO.TextFieldParser
逐行读取文件,以避免将整个内容加载到内存中,我会使用System.IO.StreamWriter
将其写回。 TextFieldParser
在内部使用StreamReader
,但处理分析的分隔字段,因此您不必这样做,如果CSV格式不简单(例如,在引用字段中具有分隔符字符),则非常有用。
我根本不会在Powershell中这样做,而是在.NET应用程序中,因为即使它们使用相同的对象,它也会比Powershell脚本快得多。
这是一个简单版本的C#,假设没有引用字段和ASCII编码:
static void Main(){
string source = @"D:\test.csv";
string dest = @"D:\test2.csv";
using ( var reader = new Microsoft.VisualBasic.FileIO.TextFieldParser( source, Encoding.ASCII ) ) {
using ( var writer = new System.IO.StreamWriter( dest, false, Encoding.ASCII ) ) {
reader.SetDelimiters( ";" );
while ( !reader.EndOfData ) {
var fields = reader.ReadFields();
swap(fields, 1, 2);
writer.WriteLine( string.Join( ";", fields ) );
}
}
}
}
static void swap( string[] arr, int a, int b ) {
string t = arr[ a ];
arr[ a ] = arr[ b ];
arr[ b ] = t;
}
以下是Powershell版本:
[void][reflection.assembly]::loadwithpartialname("Microsoft.VisualBasic")
$source = 'D:\test.csv'
$dest = 'D:\test2.csv'
$reader = new-object Microsoft.VisualBasic.FileIO.TextFieldParser $source
$writer = new-object System.IO.StreamWriter $dest
function swap($f,$a,$b){ $t = $f[$a]; $f[$a] = $f[$b]; $f[$b] = $t}
$reader.SetDelimiters(';')
while ( !$reader.EndOfData ) {
$fields = $reader.ReadFields()
swap $fields 1 2
$writer.WriteLine([string]::join(';', $fields))
}
$reader.close()
$writer.close()
我将这两个基准与具有10,000,000行的3列csv文件进行基准测试。 C#版本花了171.132秒(不到3分钟)。 Powershell版本耗时2,364.995秒(39分25秒)。
编辑:为什么我的这么长时间。
交换功能是我的Powershell版本中的一个巨大瓶颈。将其替换为'{0};{1};{2}'
式的输出,如Roman Kuzmin的答案,将其缩短至不到9分钟。将TextFieldParser
替换为将剩余部分减半,使其不到4分钟。
然而,一个.NET控制台应用程序版本的Roman Kuzmin的答案花了20秒。
答案 3 :(得分:5)
很高兴人们带来了基于纯.NET的解决方案。但是,如果可能的话,我会为简单而战。这就是为什么我赞成你们所有人;)
为什么呢?我尝试生成1.000.000记录并将其存储在CSV中,然后重新排序列。 在我的情况下生成csv比重新排序要求更高。看看结果。
重新排序列仅需1.8分钟。对我而言,这是相当不错的结果。 对我来说好吗? - >是的,我不需要尝试找出更快的解决方案,这已经足够了 - >为其他一些有趣的东西节省了我的时间;)
# generate some csv; objects have several properties
measure-command {
1..1mb |
% {
$date = get-date
New-Object PsObject -Property @{
Column1=$date
Column2=$_
Column3=$date.Ticks/$_
Hour = $date.Hour
Minute = $date.Minute
Second = $date.Second
ReadableTime = $date.ToLongTimeString()
ReadableDate = $date.ToLongDateString()
}} |
Export-Csv d:\temp\exported.csv
}
TotalMinutes : 6,100025295
# reorder the columns
measure-command {
Import-Csv d:\temp\exported.csv |
Select ReadableTime, ReadableDate, Hour, Minute, Second, Column1, Column2, Column3 |
Export-Csv d:\temp\exported2.csv
}
TotalMinutes : 2,33151559833333
答案 4 :(得分:1)
我这样做:
$new_csv = new-object system.collections.ArrayList
get-content mycsv.csv |% {
$new_csv.add((($_ -split ";")[0,2,1]) -join ";") > $nul
}
$new_csv | out-file myreordered.csv