合并多个 CSV 文件

Merge many CSV files

我有一组 500 个 csv 文件。每个文件有四列和可变行数。

我想将所有这些 csv 合并为一个公共 sheet。如果有人可以帮助我在 PowerShell 中执行此操作,那就太好了。

Sample Data in Excel 1:

Name  Age  Marks  Class
A      15   100    5
B      20   88     6

Sample Data in Excel 2:
Name   Age  Marks  Class
C      11   99      2

Output :
Name   Age   Marks   Class
A      15    100      5
B      20     88      6
C      11     99      2

在您的情况下,sort name 是可选的,具体取决于合并是否还应重新排序内容(显然,您也可以 sort 在不同的参数上)。与上述规定相同 - 所有 .csv 文件在一个目录中。

dir c:\directory_containing_your\*.csv | Import-Csv | sort name | Export-Csv -Path c:\output.csv -NoTypeInformation

来自ScriptingGuy.

如果所有 CSV 文件都在一个文件夹中,则:

$res = @()
ls *.csv | %{
  $temp = Import-CSV $_
  $res += $temp
}
$res | Export-CSV .\ALLINFO.csv -NoTypeInformation

细分:

  • $res = @() - 创建一个名为 $res 的数组来保存所有数据。这不是严格要求的。您可以通过直接附加到结果文件的方式来完成。

  • ls *.csv | - 查找文件夹中的所有CSV文件并将它们传递给下一个命令。

  • %{$temp = Import-CSV $_; $res += $temp} - 获取每个文件,将 CSV 数据导入到名为 $temp 的 holder 变量中。将$temp的内容添加到收集器变量$res中。同样,没有必要使用中间 $tamp 变量,我只是觉得这样做更清楚。

  • $res | Export-CSV .\ALLINFO.csv -NoTypeInformation - 现在所有文件的数据都在 $res 中,导出 $res 到一个新文件。

这是一个评论很多的解决方案,它在 Excel 中使用 VBA 来合并 CSV。这里的策略是这样的:

  1. 预先设置您的引用,最重要的是 strDir 变量(这是一个字符串,表示包含所有 CSV 的目录)
  2. 遍历目录
  3. 打开每个 CSV
  4. 从每个 CSV 中复制适当的内容
  5. 将内容粘贴到输出工作簿
  6. 重复循环,直到迭代完所有文件

希望对您有所帮助!

Option Explicit
Public Sub CombineCSVsInFolder()

Dim strFile As String, strDir As String
Dim wbkSource As Workbook, wbkOutput As Workbook
Dim wksSource As Worksheet, wksOutput As Worksheet
Dim lngLastRowSource As Long, lngLastRowOutput As Long
Dim rngSource As Range, rngOutput As Range
Dim blnFirst As Boolean

'Set references up-front
strDir = "c:\stack\my_csvs\" '<~ edit this line with the CSV directory
strFile = Dir(strDir)
blnFirst = True
Set wbkOutput = Workbooks.Add
Set wksOutput = wbkOutput.ActiveSheet
Application.ScreenUpdating = False

'Loop through the CSV directory
While (strFile <> "")

    'Assign source CSV files
    Set wbkSource = Workbooks.Open(strDir & strFile)
    Set wksSource = wbkSource.ActiveSheet

    'Assign boundaries of area to copy and output
    lngLastRowSource = LastRowNum(wksSource)
    lngLastRowOutput = LastRowNum(wksOutput)
    With wksOutput
        Set rngOutput = .Cells(lngLastRowOutput + 1, 1)
    End With

    'If this is the first time through, include headers, otherwise do not
    If blnFirst = False Then
        With wksSource
            Set rngSource = .Range(.Cells(2, 1), .Cells(lngLastRowSource, 4))
        End With
    'Special case for first iteration to correct source and output ranges
    Else
        With wksSource
            Set rngSource = .Range(.Cells(1, 1), .Cells(lngLastRowSource, 4))
        End With
        With wksOutput
            Set rngOutput = .Cells(1, 1)
        End With
        blnFirst = False
    End If


    'Execute copy, close source and repeat
    rngSource.Copy rngOutput
    wbkSource.Close
    strFile = Dir
Wend

'Turn screen updates back on
Application.ScreenUpdating = True

End Sub


'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'INPUT       : Sheet, the worksheet we'll search to find the last row
'OUTPUT      : Long, the last occupied row
'SPECIAL CASE: if Sheet is empty, return 1
Public Function LastRowNum(Sheet As Worksheet) As Long
    If Application.WorksheetFunction.CountA(Sheet.Cells) <> 0 Then
        LastRowNum = Sheet.Cells.Find(What:="*", _
                        LookIn:=xlFormulas, _
                        SearchOrder:=xlByRows, _
                        SearchDirection:=xlPrevious).Row
    Else
        LastRowNum = 1
    End If
End Function

如果文件很大,那么您可以将它们合并为文本文档。这比导入 csv 对象快得多,但它要求属性和它们放置的顺序在所有文件中都相同。示例:

$files = Get-ChildItem "*.csv"
#Get header
$text = @(Get-Content -Path $files[0].FullName -TotalCount 1)

$files | ForEach-Object {
    #Get text but skip header
    $text += Get-Content -Path $_.FullName | Select-Object -Skip 1
}

#Save merged csv
$text | Set-Content Output.csv

Output.csv

Name;Age;Marks;Class
A;15;100;5
B;20;88;6
C;11;99;2

您可以通过将 Get-Content 替换为 [System.IO.File]::ReadAllLines() 等来进一步优化它,但我现在跳过了它,因为它更 complicated/hard 可读。

更新:添加了替代解决方案,按照 Ansgar 的建议保存输出文件部分。

$outputfile = "Output.csv"

$files = Get-ChildItem "*.csv"

#Get header
Get-Content -Path $files[0].FullName -TotalCount 1 | Set-Content -Path $outputfile

$files | ForEach-Object {
    #Get text but skip header
    Get-Content -Path $_.FullName | Select-Object -Skip 1
} | Add-Content -Path $outputfile