web-dev-qa-db-ja.com

PowerShellを使用して複数のCSVファイルを1つにマージする

こんにちは、ディレクトリ内のすべてのcsvファイルを1つのテキストファイル(.txt)にマージするPowerShellスクリプトを探しています。すべてのcsvファイルには同じヘッダーがあり、すべてのファイルの最初の行に常に保存されます。したがって、最初のファイルからヘッダーを取得する必要がありますが、残りのファイルでは最初の行をスキップする必要があります。必要なことを正確に実行しているバッチファイルを見つけることができましたが、1つのディレクトリに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スクリプトを作成する方法はありますか?

ありがとうございました。

ジョン

20
john50

これにより、すべてのファイルが一緒に追加され、一度に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
    }
37
kemiller2002

ワンライナーを使用している場合は、各csvをImport-Csvにパイプしてから、すぐにExport-Csvにパイプできます。これにより、最初のヘッダー行が保持され、残りのファイルのヘッダー行が除外されます。また、メモリにすべてをロードしてから、マージされたcsvにダンプするのではなく、各csvを一度に1つずつ処理します。

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

これを試して、それは私のために働いた

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

バッチファイルは非常に非効率的です!これを試してみてください(驚かれることでしょう:)

_@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"')は、typeコマンドを実行し、その出力を一時ファイルにキャプチャしてからデータを読み取るために、cmd.exeをロードして実行する必要があります。これは、各入力ファイルfor /f ... in ("%%i")は、ファイルからデータを直接読み取ります。
  2. _>>_リダイレクトはファイルを開き、データを最後に追加してファイルを閉じます。これは各出力* line *で行われます。 _>_リダイレクトは、ファイルを常に開いたままにします。
2
Aacini

こちらも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))
}
1
Jan Chrbolka

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;

このアプローチの唯一の欠点は、すべてのファイルを解析することです。また、すべてのファイルをメモリにロードするため、それぞれ100 MBの4000個のファイルについて話している場合は、明らかに問題が発生します。

System.IO.FileおよびSystem.IO.StreamWriterを使用すると、パフォーマンスが向上する場合があります。

1
Bacon Bits
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
0
Randall Spies

以前のソリューションは、パフォーマンスの点で大きなcsvファイルに対して非常に非効率的であることがわかったため、ここにパフォーマンスの代替があります。

以下は、単にファイルを追加する代替手段です。

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

その後、おそらく複数のcsv-headerを削除する必要があります。

0
davidhigh
$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
Dan Arseneau

次のバッチスクリプトは非常に高速です。 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行でハングするためです。

0
dbenham