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
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
}
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
$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
.
@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
-
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. - 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.
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.
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))
}
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.
$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
}
Tipo *.csv > > > pasta\combinada.csv