シーゴの Excel 研究室

タイトル変更しました (旧称:今日を乗り切るExcel研究所)

CSV の任意の列のフィールドをダブルクォーテーションで囲むバッチ

今回は、CSVファイルに列指定でフィールドクオートするバッチを作成しました。

クォートする列を選びたい

だいぶ前の記事で、CSV の ダブルクォーテーション の囲みを付け直す方法について紹介しました。

その中で「全てのフィールド」をクォートする(タイプ4の)バッチを紹介しましたが、機会があれば「値の種類(文字列、日付)」によってクオートするバッチ(タイプ3)も作成したいと思っていました。

最近 PowerShell で CSV を扱うことがあって「フィールドの列を指定」してクォートするスクリプトを作成しましたので、これが代わりに使えそうです。

本記事ではそれを CSV のドラッグ&ドロップでも使えるよう、バッチにしておこうと思います。

ただし、処理が重いので大きなCSVファイルでは注意してください。

CVS 任意の列をクオートするバッチ

以下のバッチスクリプトは、CSV ファイルのクォート処理をして別の CSV ファイルとして保存するものです。 任意の列を指定してダブルクォーテーションの囲みを付加・除去できます。

使い方は CSV ファイルを BAT ファイルにドラッグ&ドロップするだけです。

クォートする列の指定は変換元 CSV ファイルの 1 行目で行います。

つまり、元の CSV ファイルの 1 行目だけ手作業であらかじめクォートしたいフィールドをダブルクォーテーションで囲んでおきます。

そうすると、1 行目でクォートされているフィールドの列では全ての値がクォートされ、そうでない列ではクォートがあったとしても外されます。

ただし、クオートが必須な文字(カンマ「,」、改行コード、ダブルクォーテーション「"」)を含んでいるテキストフィールドは、なし指定の列にあってもクォートされます。

1行目にクォートが一箇所もなかった場合は、結果的に必要な時だけクォート、すなわち Excel の CSV 出力と同等の形式になります。

 



 

@set a=%* & call powershell -nop "&([scriptblock]::create('#'+(gc '%~f0' -raw)))" %%a:^"=\^"%% & pause & exit /b

function quote_fields([string]$csv, [bool[]]$quoteOrNot) {
    function _zip([Object[]]$l, [Object[]]$r) {[Linq.Enumerable]::Zip($l, $r, [Func[Object, Object, Object[]]]{$args})}
    $pattern = '(?s)(?!$)(?: *("?)(?<V>(?:""|.)*?)\1 *)(?:, *("?)(?<V>(?:""|.)*?)\2 *)*(?<N>\r?\n|$)'    
    $quoter = { param([Text.RegularExpressions.Match]$match)
        if ($match.Groups["V"].Length -eq 0) {
            $match.Value
        } else {
            $vals = $match.Groups["V"].Captures.Value -replace '""','"'
            $flgs = @() + $quoteOrNot  # copy array
            [array]::Resize([ref]$flgs, $vals.Count) # pad False
            $flds = _zip $vals $flgs | foreach {
                $val, $quote = $_
                $force = $val -match ',|\r?\n|"'
                if ($quote -or $force) {
                    '"' + ($val -replace '"','""') + '"'
                } else {
                    $val
                }
            }
            ($flds -join ",") + $match.Groups["N"].Value
        }
    }
    [regex]::Replace($csv, $pattern, $quoter)
}

if ($args.count -eq 0 ) { exit 1 }

$files = Get-Item $args -Include *.csv
$quoteFlags = (Get-Content $files[0] -First 1 -Encoding UTF8) -split "," | %{$_ -match '"'}
foreach ($file in $files) {
    $file.FullName | Out-Host
    $csv = (Get-Content $file -Raw -Encoding UTF8)
    $csv = quote_fields $csv $quoteFlags
    New-Item -Path "${file}.q.csv" -Value $csv -Force > $null
}

【使い方】

  1. 上記バッチスクリプトをテキストエディタにコピー&ペーストし、任意の名称で BAT ファイルとして保存します
    • 拡張子(ファイル名の最後)を .bat とします
  2. 変換元の CSV ファイルを編集し、1行目で任意のフィールドをクォートしておきます
    • CSV の1行目にはカンマ(,)や改行を含むフィールドがないようにしてください
  3. CSV ファイルを BAT ファイルにドラッグ&ドロップします
  4. クォート済みの CSV ファイルが出力されます

複数の CSV ファイルをまとめてドラッグ&ドロップすることで、一括で処理することができます。 その場合、クォート処理判定に使われるのは最初の CSV ファイルのみで、後続の CSV ファイルでもその指定が採用されます。

例えば、クォート指定のために1行だけのCSVを用意して、最初のファイルとして指定すれば、変換元 CSV を編集する必要はなくなります。

ただし、ドラッグ&ドロップでのファイルの順番をコントロールするのは難しいので、その場合コマンドプロンプトから実行した方が確実でしょう。

以下注意事項です

  • ファイルの文字エンコーディングは「BOMなしUTF-8」です
  • フィールド内改行があってもデータは壊れません。そのクォート状態のままです
  • フィールド内で値の前後にある半角スペースは削除されてしまいます
  • レコード(行)によって列数がそろっていない場合、先頭行の列数より多ければ、はみ出した後ろのフィールドは「クォートなし」になります。
  • コマンドプロンプトから実行する場合、引数に与えるファイル名にはワイルドカートが使えます。

【免責】
本ブログ掲載のバッチやスクリプトは、その処理結果の正確性・正当性を保証するものはありません。万一、本バッチの不具合や誤操作により損害や損失が発生した場合も、本ブログの著者は一切の責任を負いかねますのであらかじめご了承ください。
また、今回のバッチやスクリプトは、実務での使用実績はありません。あくまで個人の作業効率化に少しでも寄与することを期待したものですので、組織での業務に組み込まれるようなことがないよう、個人の責任の範囲内でのみご使用ください。 【注意】 セキュリティのため、本記事のバッチに限らず、BAT ファイルは、メールやチャットに添付したり Web リンクや共有フォルダからダウンロードできるようにしないでください。また、そのような経路で入手した BAT ファイルも、内容を確認せずに実行してはいけません。

まとめ

今回タイプ3の CSV に変換するため、値の型ではなく、列指定でダブルクォーテーションでフィールド値を囲むバッチを作成しました。

どれだけ需要があるかわかりませんが、個人的に長年心残りだったネタが一つ減って気が済みました。

もし使ってみて、不具合や使い勝手の要望などありましたら下のコメント欄か Twitter にてお知らせください。

関連記事

www.shegolab.jp

www.shegolab.jp

変更履歴

  • [2022/10/03]  公開
  • [2022/10/15]  一部文面修正