Juntar vários ficheiros CSV num só com o PowerShell

Estou à procura de um programa powershell que reunisse todos os ficheiros csv numa pasta num ficheiro de texto (.txt). Todos os arquivos csv têm o mesmo cabeçalho que é sempre armazenado em uma primeira linha de cada arquivo. Então eu preciso tomar o cabeçalho do primeiro arquivo, mas no resto dos arquivos a primeira linha deve ser ignorada. Eu fui capaz de encontrar o arquivo batch que está fazendo exatamente o que eu preciso, mas eu tenho mais de 4000 arquivos csv em um único diretório e leva mais de 45 minutos para fazer o trabalho.

@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
 )

alguma sugestão de como criar script powershell que seria mais eficiente do que este código de lote?

Obrigado.

John

Author: john50, 2015-01-12

9 answers

Isto irá adicionar todos os ficheiros juntos lendo-os um de cada vez:

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

# Placed on seperate lines for readability

Este irá colocar uma nova linha no final de cada entrada de ficheiro se precisar dela:

get-childItem "YOUR_DIRECTORY\*.txt" | foreach
{[System.IO.File]::AppendAllText("YOUR_DESTINATION_FILE", 
[System.IO.File]::ReadAllText($_.FullName) + [System.Environment]::NewLine)}
Saltando a primeira linha:
$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
    }
 23
Author: kemiller2002, 2015-01-12 02:20:18

Se está atrás de um invólucro, pode canalizar cada csv para um Import-Csv e, em seguida, imediatamente encaminhar isso para Export-Csv. Isto irá manter a linha inicial do cabeçalho e excluir as restantes linhas de cabeçalho dos ficheiros. Ele também vai processar cada csv um de cada vez, em vez de carregar tudo na memória e, em seguida, despejá-los em seu CSV fundido.

Get-ChildItem -Filter *.csv | Select-Object -ExpandProperty FullName | Import-Csv | Export-Csv .\merged\merged.csv -NoTypeInformation -Append
 14
Author: stinkyfriend, 2017-05-15 06:27:10
Isto é bastante trivial em 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;

A única desvantagem desta abordagem é que ela analisa cada ficheiro. Ele também carrega todos os arquivos em memória, então se estamos falando de cerca de 4000 arquivos que são 100 MB cada um você obviamente vai ter problemas.

Podes ter melhor desempenho com System.IO.File e System.IO.StreamWriter.

 1
Author: Bacon Bits, 2015-01-11 23:10:16
O teu ficheiro em lote é muito ineficiente! Tenta este (vais ficar surpreendido:)
@echo off
ECHO Set working directory
cd /d %~dp0
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

Como isto é um improviso

  1. for /f ... in ('type "%%i"') necessita de carregar e executar cmd.o exe, a fim de executar o comando do tipo, captura o seu resultado num ficheiro temporário e, em seguida, lê os dados dele, e isto é feito com cada ficheiro de entrada . {[2] } lê directamente os dados do ficheiro.
  2. o redireccionamento >> Abre o ficheiro, adiciona os dados no fim e fecha o ficheiro, e isto é feito com cada saída * Linha * . O redireccionamento > mantém o ficheiro aberto o tempo todo.
 1
Author: Aacini, 2015-01-12 19:27:00

Eu achei as soluções anteriores bastante ineficientes para grandes arquivos csv em termos de desempenho, então aqui está uma alternativa performant.

Aqui está uma alternativa que simplesmente adiciona os arquivos:

cmd /c copy  ((gci "YOUR_DIRECTORY\*.csv" -Name) -join '+') "YOUR_OUTPUT_FILE.csv" 
Depois disso, deves querer livrar-te dos vários cabeçalhos csv.
 1
Author: davidhigh, 2017-05-23 06:49:04

Aqui está uma versão que também usa o 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))
}
 0
Author: Jan Chrbolka, 2015-01-12 05:03:28

O seguinte script em lote é muito rápido. Ele deve funcionar bem, desde que nenhum dos seus arquivos CSV contêm caracteres de tabulação, e todos os arquivos CSV fonte têm menos de 64k linhas.

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

A razão para as restrições é que mais converte tabs em uma série de espaços, e redirecionando mais pendura em linhas 64k.

 0
Author: dbenham, 2015-01-12 05:22:04
$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
}
 0
Author: Dan Arseneau, 2017-03-10 16:05:30

Tipo *.csv > > > pasta\combinada.csv

 -2
Author: Kent, 2018-04-09 10:10:11