Hallo, ich suche ein Powershell-Skript, das alle CSV-Dateien in einem Verzeichnis in einer Textdatei (.txt) zusammenführen würde. Alle csv-Dateien haben denselben Header, der immer in einer ersten Zeile jeder Datei gespeichert wird. Ich muss also den Header aus der ersten Datei nehmen, aber in den restlichen Dateien sollte die erste Zeile übersprungen werden. Ich konnte eine Batch-Datei finden, die genau das tut, was ich brauche, aber ich habe mehr als 4000 CSV-Dateien in einem einzigen Verzeichnis, und die Arbeit dauert mehr als 45 Minuten.
@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
)
Jeder Vorschlag, wie man ein Powershell-Skript erstellt, das effizienter ist als dieser Batch-Code?
Vielen Dank.
John
Dies fügt alle Dateien zusammen und liest sie einzeln ein:
get-childItem "YOUR_DIRECTORY\*.txt"
| foreach {[System.IO.File]::AppendAllText
("YOUR_DESTINATION_FILE", [System.IO.File]::ReadAllText($_.FullName))}
# Placed on seperate lines for readability
Diese wird am Ende jedes Dateieintrags eine neue Zeile einfügen, wenn Sie diese benötigen:
get-childItem "YOUR_DIRECTORY\*.txt" | foreach
{[System.IO.File]::AppendAllText("YOUR_DESTINATION_FILE",
[System.IO.File]::ReadAllText($_.FullName) + [System.Environment]::NewLine)}
Überspringen der ersten Zeile:
$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
}
Wenn Sie auf der Suche nach einem Einzeiler sind, können Sie jede csv an einen Import-Csv
weiterleiten und dann sofort an Export-Csv
weiterleiten. Dadurch wird die erste Kopfzeile beibehalten und die verbleibenden Kopfzeilen der Dateien ausgeschlossen. Es wird auch jede csv nacheinander verarbeiten, anstatt alle in den Speicher zu laden und sie dann in die zusammengeführte csv zu speichern.
Get-ChildItem -Filter *.csv | Select-Object -ExpandProperty FullName | Import-Csv | Export-Csv .\merged\merged.csv -NoTypeInformation -Append
Ihre Batch-Datei ist ziemlich ineffizient! Versuchen Sie es hier (Sie werden überrascht sein :)
@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
Wie ist das eine Verbesserung
for /f ... in ('type "%%i"')
muss cmd.exe geladen und ausgeführt werden, um den type-Befehl auszuführen, seine Ausgabe in einer temporären Datei zu erfassen und Daten daraus zu lesen. Dies geschieht mit jeder Eingabedatei. for /f ... in ("%%i")
liest direkt Daten aus der Datei. >>
-Umleitung öffnet die Datei, fügt die Daten am Ende hinzu und schließt die Datei. Dies geschieht mit jeder Ausgabe * Zeile *. Die >
-Umleitung hält die Datei ständig geöffnet.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
Die bisherigen Lösungen waren für große CSV-Dateien in Bezug auf die Leistung ziemlich ineffizient, daher ist hier eine performante Alternative .
Hier ist eine Alternative, die die Dateien einfach anfügt:
cmd /c copy ((gci "YOUR_DIRECTORY\*.csv" -Name) -join '+') "YOUR_OUTPUT_FILE.csv"
Danach möchten Sie wahrscheinlich die mehreren CSV-Header loswerden.
Dies ist in PowerShell ziemlich trivial.
$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;
Der einzige Nachteil dieses Ansatzes ist, dass jede Datei analysiert wird. Außerdem werden alle Dateien in den Speicher geladen. Wenn Sie also über 4000 Dateien mit jeweils 100 MB sprechen, werden Sie offensichtlich auf Probleme stoßen.
Mit System.IO.File
und System.IO.StreamWriter
erzielen Sie möglicherweise eine bessere Leistung.
Versuchen Sie das, es hat bei mir funktioniert
Get-Content *.csv| Add-Content output.csv
Hier ist eine Version, die auch System.IO.File verwendet,
$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))
}
$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
}
Das folgende Batch-Skript ist sehr schnell. Es sollte gut funktionieren, solange keine Ihrer CSV-Dateien Tabulatorzeichen enthält und alle Quell-CSV-Dateien weniger als 64.000 Zeilen enthalten.
@echo off
set "skip="
>summary.txt (
for %%F in (*.csv) do if defined skip (
more +1 "%%F"
) else (
type "%%F"
set skip=1
)
)
Der Grund für die Einschränkungen ist, dass MORE Registerkarten in eine Reihe von Leerzeichen konvertiert und MORE umgeleitet mit 64-KB-Zeilen hängt.