Объединение нескольких файлов CSV в один с помощью PowerShell

Здравствуйте, я ищу скрипт powershell, который объединил бы все файлы csv в каталоге в один текстовый файл (.txt). Все файлы csv имеют одинаковый заголовок, который всегда хранится в первой строке каждого файла. Поэтому мне нужно взять заголовок из первого файла, но в остальных файлах первая строка должна быть пропущена. Я смог найти пакетный файл, который делает именно то, что мне нужно, но у меня более 4000 файлов csv в одном каталоге, и для выполнения этой задачи требуется более 45 минут.

@echo off
ECHO Set working directory
cd /d %~dp0
Deleting existing combined file
del summary.txt
setlocal ENABLEDELAYEDEXPANSION
set cnt=1
for %%i in (*.csv) do (
 if !cnt!==1 (
 for /f "delims=" %%j in ('type "%%i"') do echo %%j >> summary.txt
) else (
 for /f "skip=1 delims=" %%j in ('type "%%i"') do echo %%j >> summary.txt
 )
 set /a cnt+=1
 )

Любое предложение создать скрипт powershell, который был бы более эффективным, чем этот пакетный код?

Спасибо.

Джон

Ответ 1

Это добавит все файлы вместе, читая их по одному за раз:

get-childItem "YOUR_DIRECTORY\*.txt" 
| foreach {[System.IO.File]::AppendAllText
 ("YOUR_DESTINATION_FILE", [System.IO.File]::ReadAllText($_.FullName))}

# Placed on seperate lines for readability

В этом случае в конце каждой записи файла будет добавлена новая строка:

get-childItem "YOUR_DIRECTORY\*.txt" | foreach
{[System.IO.File]::AppendAllText("YOUR_DESTINATION_FILE", 
[System.IO.File]::ReadAllText($_.FullName) + [System.Environment]::NewLine)}

Пропуск первой строки:

$getFirstLine = $true

get-childItem "YOUR_DIRECTORY\*.txt" | foreach {
    $filePath = $_

    $lines =  $lines = Get-Content $filePath  
    $linesToWrite = switch($getFirstLine) {
           $true  {$lines}
           $false {$lines | Select -Skip 1}

    }

    $getFirstLine = $false
    Add-Content "YOUR_DESTINATION_FILE" $linesToWrite
    }

Ответ 2

Если вы используете однострочный канал, вы можете Import-Csv каждый csv к Import-Csv а затем немедленно Export-Csv в Export-Csv. Это сохранит исходную строку заголовка и исключит оставшиеся строки заголовков файлов. Он также будет обрабатывать каждый csv один за раз, а не загружать все в память и затем сбрасывать их в ваш объединенный csv.

Get-ChildItem -Filter *.csv | Select-Object -ExpandProperty FullName | Import-Csv | Export-Csv .\merged\merged.csv -NoTypeInformation -Append

Ответ 3

Попробуйте это, у меня получилось

Get-Content *.csv| Add-Content output.csv

Ответ 4

Ваш пакетный файл довольно неэффективен! Попробуйте это (вы будете удивлены :)

@echo off
ECHO Set working directory
cd /d %~dp0
ECHO Deleting existing combined file
del summary.txt
setlocal
for %%i in (*.csv) do set /P "header=" < "%%i" & goto continue
:continue

(
   echo %header%
   for %%i in (*.csv) do (
      for /f "usebackq skip=1 delims=" %%j in ("%%i") do echo %%j
   )
) > summary.txt

Как это улучшение

  1. for /f ... in ('type "%%i"') требует загрузить и выполнить cmd.exe, чтобы выполнить команду типа, записать ее вывод во временный файл, а затем прочитать данные из него, и это делается с каждым входным файлом. for /f ... in ("%%i") непосредственно считывает данные из файла.
  2. Перенаправление >> открывает файл, добавляет данные в конце и закрывает файл, и это делается с каждой выходной * строкой *. Перенаправление > сохраняет файл открытым все время.

Ответ 5

Это довольно тривиально в PowerShell.

$CSVFolder = 'C:\Path\to\your\files';
$OutputFile = 'C:\Path\to\output\file.txt';

$CSV= @();

Get-ChildItem -Path $CSVFolder -Filter *.csv | ForEach-Object { 
    $CSV += @(Import-Csv -Path $_)
}

$CSV | Export-Csv -Path $OutputFile -NoTypeInformation -Force;

Единственным недостатком этого подхода является то, что он анализирует каждый файл. Он также загружает все файлы в память, поэтому, если мы говорим о 4000 файлах по 100 МБ каждый, вы, очевидно, столкнетесь с проблемами.

Вы можете получить более высокую производительность с помощью System.IO.File и System.IO.StreamWriter.

Ответ 6

Вот версия, также использующая System.IO.File,

$result = "c:\temp\result.txt"
$csvs = get-childItem "c:\temp\*.csv" 
#read and write CSV header
[System.IO.File]::WriteAllLines($result,[System.IO.File]::ReadAllLines($csvs[0])[0])
#read and append file contents minus header
foreach ($csv in $csvs)  {
    $lines = [System.IO.File]::ReadAllLines($csv)
    [System.IO.File]::AppendAllText($result, ($lines[1..$lines.Length] | Out-String))
}

Ответ 7

$pathin = 'c:\Folder\With\CSVs'
$pathout = 'c:\exported.txt'
$list = Get-ChildItem -Path $pathin | select FullName
foreach($file in $list){
    Import-Csv -Path $file.FullName | Export-Csv -Path $pathout -Append -NoTypeInformation
}

Ответ 8

Я обнаружил, что предыдущие решения довольно неэффективны для больших csv файлов с точки зрения производительности, так что вот альтернатива для исполнителей.

Вот альтернатива, которая просто добавляет файлы:

cmd /c copy  ((gci "YOUR_DIRECTORY\*.csv" -Name) -join '+') "YOUR_OUTPUT_FILE.csv" 

После этого вы, вероятно, захотите избавиться от нескольких заголовков csv.

Ответ 9

Get-ChildItem *.csv|select -First 1|Get-Content|select -First 1|Out-File -FilePath .\input.csv -Force #Get the header from one of the CSV Files, write it to input.csv
Get-ChildItem *.csv|foreach {Get-Content $_|select -Skip 1|Out-File -FilePath .\Input.csv -Append} #Get the content of each file, excluding the first line and append it to input.csv

Ответ 10

Следующий пакетный скрипт очень быстрый. Он должен работать хорошо, если ни один из ваших файлов CSV не содержит символов табуляции, а все исходные CSV файлы имеют менее чем 64k строк.

@echo off
set "skip="
>summary.txt (
  for %%F in (*.csv) do if defined skip (
    more +1 "%%F"
  ) else (
    type "%%F"
    set skip=1
  )
)

Причиной ограничений является то, что MORE конвертирует вкладки в ряд пробелов и перенаправляет MORE на 64k строк.

Ответ 11

type *.csv >> folder\combination.csv