如何使用powershell对CSV列重新排序

如何使用powershell对CSV列重新排序,powershell,csv,Powershell,Csv,输入文件: column1;column2;column3 data1a;data2a;data3a data1b;data2b;data3b 目标:输出具有重新排序列的文件,例如 column1;column3;column2 ... 更新问题: 使用powershell解决此问题的好方法是什么。 我知道存在与CSV相关的cmdlet,但这些cmdlet存在局限性。 请注意,记录的顺序不需要更改,因此不需要将整个输入/输出文件加载到内存中。我会这样做: Import-CSV C:\Path

输入文件:

column1;column2;column3
data1a;data2a;data3a
data1b;data2b;data3b
目标:输出具有重新排序列的文件,例如

column1;column3;column2
...
更新问题: 使用powershell解决此问题的好方法是什么。 我知道存在与CSV相关的cmdlet,但这些cmdlet存在局限性。 请注意,记录的顺序不需要更改,因此不需要将整个输入/输出文件加载到内存中。

我会这样做:

Import-CSV C:\Path\To\Original.csv | Select-Object Column1, Column3, Column2 | Export-CSV C:\Path\To\Newfile.csv
$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

编辑:下面的基准信息

我不会使用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()
我将这两个文件与一个包含10000000行的3列csv文件进行了基准测试。C版本花了171.132秒(不到3分钟)。Powershell版本耗时2364.995秒(39分25秒)

编辑:为什么我的花了这么长时间

交换功能是我的Powershell版本中的一个巨大瓶颈。将其替换为
'{0};{1};{2} 
-类似于罗曼·库兹明的答案的输出将时间缩短到9分钟以内。更换
TextFieldParser
后,剩余时间减少了一半,不足4分钟


然而,Roman Kuzmin回答的.NET控制台应用程序版本花费了20秒。

以下是适用于数百万条记录的解决方案(假设您的数据没有嵌入“;”)


人们提出了基于纯.NET的解决方案,这真是太好了。然而,如果可能的话,我会为简单性而奋斗。这就是为什么我投了你们所有人的票;)

为什么??我尝试生成1.000.000条记录并将其存储在CSV中,然后对列进行重新排序。 在我的例子中,生成csv比重新排序要求更高。看看结果

只需1,8分钟就可以对列进行重新排序。对我来说,这是一个相当不错的结果。对我合适吗?->是的,我不需要尝试找出更快的解决方案,这已经足够了->节省了我的时间去做其他有趣的事情;)



问题到底是什么?如何对列重新排序,或者如何使其在数百万条记录的情况下工作?(我有一些经验,直截了当的解决方案不能很好地工作)。没错,我只是简单地解决了列的重新排序问题。如果文件大小是一个问题,那么解决方案肯定会变得更加复杂。对于数百万行,我会将文件加载到数据库中,并按要求的顺序导出行。MSSQL有非常复杂的导入/导出工具,但任何数据库都可以。测量,始终。。您花在创建解决方案上的时间越少,您就越能享受生活;)(见我的答案为什么)见我向作者提出的问题。见我对你向作者提出的问题的回答:)为了澄清,我意识到,我的答案实际上只是这篇文章完整答案的一半。不过,我打算把它留在这里,这样以后任何人在这个话题上闲逛,处理较小的文件时,都会对他们的问题有一个更简单的答案,
Import CSV
在管道传输时不会将整个文件读取到内存中,而且此解决方案比我的Powershell版本(32分44秒)更快<代码>导出CSV可能需要被告知不要将每个字段都括在引号中:由于
+=
方法的原因,需要花费数小时处理一百万条记录。试试:)这是假设它没有耗尽内存,因为
get content
将首先将整个内容读取到内存中。因此将导入csv。不同之处在于导入csv将以[object[]的形式存储在内存中,这将以[string[]的形式存储。[string[]的内存需求应该大大减少。Roman正确地认为+=是一个严重的性能问题。更改为arraylist类型并改用.add方法似乎要快得多。这看起来是一个更好的解决方案。如果我们替换2行
$data=$writer.WriteLine…
有一个稍微神秘一点的
$writer.WriteLine('{0};{2};{1}',$line.Split(“;”)
我想说这更具可读性。它将索引放在一起,使位移更容易掌握。我同意这是一个针对这组数据的快速解决方案。但是,它可能不会处理字段数据中出现字段分隔符的带引号的字段。这不适用于文本限定符。如果限定文本中出现字段分隔符或CRLF,则解决方案中断。
$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()
# 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