0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CSV変換

0
Last updated at Posted at 2026-09-04

#PythonのCSV処理のメモ

指定した列の値をKV型辞書ファイルの値で置換する

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
replace_by_kv.py

CSVファイルの指定した列の値を、KVファイル(ヘッダー無し・2列)の
1列目とマッチしたら2列目の値に置換して出力する。

使用例:
    python replace_by_kv.py -i data.csv -k kv.csv -c 3 -o out.csv
    python replace_by_kv.py -i data.csv -k kv.csv -c 商品コード -o out.csv --has-header
"""

import argparse
import codecs
import csv
import os
import sys

# BOM無しの場合に試す順(UTF-8を先に判定し、失敗したらcp932)
ENCODING_CANDIDATES = ["utf-8", "cp932"]


def detect_encoding(path, specified=None):
    """指定が無ければ候補を順に試して読めるエンコーディングを返す"""
    if specified and specified.lower() != "auto":
        return specified
    with open(path, "rb") as f:
        raw = f.read()
    if raw.startswith(codecs.BOM_UTF8):
        return "utf-8-sig"
    for enc in ENCODING_CANDIDATES:
        try:
            raw.decode(enc)
            return enc
        except UnicodeDecodeError:
            continue
    raise UnicodeDecodeError(
        "auto", b"", 0, 1,
        "エンコーディングを判定できません。--encoding で明示してください: " + path
    )


def load_kv(path, encoding, strip_values=True):
    """KVファイル(ヘッダー無し2列)を辞書として読み込む"""
    mapping = {}
    with open(path, "r", encoding=encoding, newline="") as f:
        for lineno, row in enumerate(csv.reader(f), start=1):
            if not row or all(c.strip() == "" for c in row):
                continue  # 空行はスキップ
            if len(row) < 2:
                print(f"[警告] KVファイル {lineno} 行目: 列が2つ未満のためスキップ: {row}",
                      file=sys.stderr)
                continue
            key, value = row[0], row[1]
            if strip_values:
                key, value = key.strip(), value.strip()
            if key in mapping and mapping[key] != value:
                print(f"[警告] KVファイル {lineno} 行目: キー '{key}' が重複。"
                      f"後の値 '{value}' で上書きします。", file=sys.stderr)
            mapping[key] = value
    return mapping


def resolve_column(col_spec, header):
    """
    列指定を0始まりのインデックスに変換する。
    数字なら1始まりの列番号、それ以外はヘッダー名として扱う。
    """
    if col_spec.isdigit():
        idx = int(col_spec) - 1
        if idx < 0:
            raise ValueError("列番号は1以上を指定してください。")
        return idx
    if header is None:
        raise ValueError("列名で指定する場合は --has-header が必要です。")
    if col_spec not in header:
        raise ValueError(f"列名 '{col_spec}' がヘッダーに見つかりません。ヘッダー: {header}")
    return header.index(col_spec)


def main():
    parser = argparse.ArgumentParser(
        description="CSVの指定列をKVファイル(ヘッダー無し2列)で置換します。")
    parser.add_argument("-i", "--input", required=True, help="入力CSVファイル")
    parser.add_argument("-k", "--kv", required=True, help="KVファイル(ヘッダー無し2列)")
    parser.add_argument("-o", "--output", required=True, help="出力CSVファイル")
    parser.add_argument("-c", "--column", required=True,
                        help="置換対象の列。1始まりの列番号、または列名(--has-header時)")
    parser.add_argument("--has-header", action="store_true",
                        help="入力CSVの1行目をヘッダーとして扱う(そのまま出力)")
    parser.add_argument("--encoding", default="auto",
                        help="入出力の文字コード。既定はauto(utf-8-sig/cp932を自動判定)")
    parser.add_argument("--output-encoding", default=None,
                        help="出力の文字コード。未指定なら入力と同じ")
    parser.add_argument("--no-strip", action="store_true",
                        help="比較時に前後の空白を除去しない")
    parser.add_argument("--report-unmatched", action="store_true",
                        help="マッチしなかった値を標準エラーに一覧表示する")
    args = parser.parse_args()

    for path in (args.input, args.kv):
        if not os.path.isfile(path):
            print(f"[エラー] ファイルが見つかりません: {path}", file=sys.stderr)
            return 1

    strip = not args.no_strip

    try:
        in_enc = detect_encoding(args.input, args.encoding)
        kv_enc = detect_encoding(args.kv, args.encoding)
    except UnicodeDecodeError as e:
        print(f"[エラー] {e}", file=sys.stderr)
        return 1
    out_enc = args.output_encoding or in_enc

    mapping = load_kv(args.kv, kv_enc, strip_values=strip)
    if not mapping:
        print("[エラー] KVファイルから有効な行を読み込めませんでした。", file=sys.stderr)
        return 1

    replaced = 0
    total = 0
    unmatched = set()

    # newline="" はCSV読み書きの必須指定(Windowsでの改行崩れ防止)
    with open(args.input, "r", encoding=in_enc, newline="") as fin, \
         open(args.output, "w", encoding=out_enc, newline="") as fout:
        reader = csv.reader(fin)
        writer = csv.writer(fout)

        header = None
        if args.has_header:
            try:
                header = next(reader)
            except StopIteration:
                print("[エラー] 入力CSVが空です。", file=sys.stderr)
                return 1

        try:
            col = resolve_column(args.column, header)
        except ValueError as e:
            print(f"[エラー] {e}", file=sys.stderr)
            return 1

        if header is not None:
            writer.writerow(header)

        for lineno, row in enumerate(reader, start=2 if header else 1):
            if len(row) <= col:
                print(f"[警告] {lineno} 行目: 列数が足りないためそのまま出力します。",
                      file=sys.stderr)
                writer.writerow(row)
                continue

            total += 1
            key = row[col].strip() if strip else row[col]
            if key in mapping:
                row[col] = mapping[key]
                replaced += 1
            else:
                unmatched.add(key)
            writer.writerow(row)

    print(f"完了: {args.output}")
    print(f"  対象行数 : {total}")
    print(f"  置換件数 : {replaced}")
    print(f"  未一致   : {total - replaced}")
    print(f"  文字コード: 入力={in_enc} / 出力={out_enc}")

    if args.report_unmatched and unmatched:
        print("--- 未一致の値 ---", file=sys.stderr)
        for v in sorted(unmatched):
            print(v, file=sys.stderr)

    return 0


if __name__ == "__main__":
    sys.exit(main())

2つのCSVファイルを交互に挟み込む形でマージする

# -*- coding: utf-8 -*-
"""
ヘッダー無しCSVファイル2つを、1行ずつ交互に挟み込んでマージする。

出力の並び:
    A_1行目, B_1行目, A_2行目, B_2行目, ...

使い方:
  1) 下の設定を書き換えてダブルクリック実行 or  python merge_csv.py
  2) コマンドライン指定:
        python merge_csv.py A.csv B.csv merged.csv
"""

import csv
import sys
from itertools import zip_longest

# ----------------- 設定(ここを書き換える) -----------------
FILE_A = r"C:\data\A.csv"
FILE_B = r"C:\data\B.csv"
FILE_OUT = r"C:\data\merged.csv"

# Excelで作った日本語CSVなら "cp932"、UTF-8のCSVなら "utf-8-sig"
ENCODING_IN = "cp932"
ENCODING_OUT = "cp932"
# ------------------------------------------------------------

_MISSING = object()


def read_rows(path, encoding):
    """CSVを読み込んで行のリストを返す。"""
    with open(path, "r", encoding=encoding, newline="") as f:
        return list(csv.reader(f))


def interleave(rows_a, rows_b):
    """2つの行リストを1行ずつ交互に並べる。行数が違っても余りはそのまま出力。"""
    merged = []
    for row_a, row_b in zip_longest(rows_a, rows_b, fillvalue=_MISSING):
        if row_a is not _MISSING:
            merged.append(row_a)
        if row_b is not _MISSING:
            merged.append(row_b)
    return merged


def write_rows(path, rows, encoding):
    """CSVとして書き出す。newline="" は空行混入を防ぐため必須。"""
    with open(path, "w", encoding=encoding, newline="") as f:
        csv.writer(f).writerows(rows)


def main():
    if len(sys.argv) == 4:
        file_a, file_b, file_out = sys.argv[1], sys.argv[2], sys.argv[3]
    elif len(sys.argv) == 1:
        file_a, file_b, file_out = FILE_A, FILE_B, FILE_OUT
    else:
        print("使い方: python merge_csv.py A.csv B.csv merged.csv")
        return 1

    try:
        rows_a = read_rows(file_a, ENCODING_IN)
        rows_b = read_rows(file_b, ENCODING_IN)
    except FileNotFoundError as e:
        print("ファイルが見つかりません:", e.filename)
        return 1
    except UnicodeDecodeError:
        print("文字コードが違う可能性があります。ENCODING_IN を 'utf-8-sig' に変えて再実行してください。")
        return 1

    if len(rows_a) != len(rows_b):
        print("警告: 行数が異なります (A={}行, B={}行)。余った行はそのまま末尾に出力します。"
              .format(len(rows_a), len(rows_b)))

    merged = interleave(rows_a, rows_b)
    write_rows(file_out, merged, ENCODING_OUT)

    print("完了: {} 行を書き出しました -> {}".format(len(merged), file_out))
    return 0


if __name__ == "__main__":
    sys.exit(main())

指定した列を指定した桁数で0パディング

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
zero_pad_csv.py

ヘッダー無しCSVファイルの指定した列を、指定した桁数になるまで
先頭に "0" を詰めて(ゼロパディングして)出力するツール。

例) 値が "3" で桁数が 5 のとき -> "00003"

使い方:
    python zero_pad_csv.py input.csv output.csv --column 2 --width 5
    python zero_pad_csv.py input.csv --column 1,3 --width 8 --encoding cp932
"""

import argparse
import csv
import os
import sys


def parse_columns(text):
    """ "2""1,3,5" のような文字列を 1 始まりの列番号リストに変換する。"""
    columns = []
    for part in text.split(","):
        part = part.strip()
        if not part:
            continue
        if not part.isdigit():
            raise argparse.ArgumentTypeError(
                "列番号は 1 以上の整数(カンマ区切り)で指定してください: %r" % part
            )
        number = int(part)
        if number < 1:
            raise argparse.ArgumentTypeError("列番号は 1 以上で指定してください: %d" % number)
        columns.append(number)
    if not columns:
        raise argparse.ArgumentTypeError("列番号が指定されていません。")
    return columns


def pad_value(value, width, strip_spaces):
    """1 つのセルの値をゼロパディングして返す。"""
    if strip_spaces:
        value = value.strip()
    # 空文字はそのまま(必要なら下の 1 行を有効にして "00000" にできます)
    # if value == "":
    #     return "0" * width
    if value == "" or len(value) >= width:
        return value
    return value.rjust(width, "0")


def convert(src, dst, columns, width, encoding, delimiter, strip_spaces, quote_all):
    """CSV を 1 行ずつ読み込み、指定列をパディングして書き出す。"""
    zero_based = [c - 1 for c in columns]
    quoting = csv.QUOTE_ALL if quote_all else csv.QUOTE_MINIMAL
    changed = 0
    total = 0
    short_rows = 0

    # 読み込み時のみ BOM を許容する(出力はBOM 無し)
    read_encoding = encoding
    if encoding.lower().replace("_", "-") in ("utf-8", "utf8"):
        read_encoding = "utf-8-sig"

    # newline="" は csv モジュール使用時の必須指定(改行の二重出力を防ぐ)
    with open(src, "r", encoding=read_encoding, newline="") as fin, \
         open(dst, "w", encoding=encoding, newline="") as fout:
        reader = csv.reader(fin, delimiter=delimiter)
        writer = csv.writer(fout, delimiter=delimiter, quoting=quoting,
                            lineterminator="\r\n")
        for row_number, row in enumerate(reader, start=1):
            total += 1
            for index in zero_based:
                if index >= len(row):
                    short_rows += 1
                    print("警告: %d 行目に %d 列目がありません。スキップします。"
                          % (row_number, index + 1), file=sys.stderr)
                    continue
                before = row[index]
                after = pad_value(before, width, strip_spaces)
                if before != after:
                    row[index] = after
                    changed += 1
            writer.writerow(row)

    return total, changed, short_rows


def main():
    parser = argparse.ArgumentParser(
        description="ヘッダー無しCSVの指定列を指定桁数までゼロパディングします。",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="例: python zero_pad_csv.py in.csv out.csv -c 2 -w 5",
    )
    parser.add_argument("input", help="入力CSVファイル(ヘッダー無し)")
    parser.add_argument("output", nargs="?",
                        help="出力CSVファイル(省略時は <入力名>_padded.csv)")
    parser.add_argument("-c", "--column", required=True, type=parse_columns,
                        help="対象の列番号。1 始まり。カンマ区切りで複数指定可 (例: 2 または 1,3)")
    parser.add_argument("-w", "--width", required=True, type=int,
                        help="桁数(この桁数になるまで先頭に 0 を詰める)")
    parser.add_argument("-e", "--encoding", default="utf-8",
                        help="文字コード。既定: utf-8(BOM無し)。"
                             "Excel由来の日本語CSVは cp932、BOM付きは utf-8-sig を指定")
    parser.add_argument("-d", "--delimiter", default=",",
                        help="区切り文字。既定: ,(タブは \\t と指定)")
    parser.add_argument("--strip", action="store_true",
                        help="値の前後の空白を除去してから判定・パディングする")
    parser.add_argument("--quote-all", action="store_true",
                        help="出力時に全項目をダブルクォートで囲む")
    args = parser.parse_args()

    if args.width < 1:
        parser.error("--width は 1 以上を指定してください。")

    if args.delimiter == "\\t":
        args.delimiter = "\t"

    if not os.path.isfile(args.input):
        print("エラー: 入力ファイルが見つかりません: %s" % args.input, file=sys.stderr)
        return 1

    output = args.output
    if not output:
        base, ext = os.path.splitext(args.input)
        output = base + "_padded" + (ext or ".csv")

    if os.path.abspath(output) == os.path.abspath(args.input):
        print("エラー: 入力ファイルと出力ファイルが同一です。", file=sys.stderr)
        return 1

    try:
        total, changed, short_rows = convert(
            args.input, output, args.column, args.width,
            args.encoding, args.delimiter, args.strip, args.quote_all,
        )
    except UnicodeDecodeError:
        print("エラー: 文字コードを読み取れませんでした。"
              "--encoding cp932 などを試してください。", file=sys.stderr)
        return 1
    except OSError as e:
        print("エラー: ファイル操作に失敗しました: %s" % e, file=sys.stderr)
        return 1

    print("完了: %s -> %s" % (args.input, output))
    print("  処理行数        : %d" % total)
    print("  パディングした件数: %d" % changed)
    if short_rows:
        print("  列が足りない箇所  : %d" % short_rows)
    return 0


if __name__ == "__main__":
    sys.exit(main())

CSVソート

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
sort_csv.py
CSVファイルを、指定した複数の列をソートキーとして並べ替えます。

【使い方】
    python sort_csv.py 入力.csv 出力.csv --key 列名1 --key 列名2:desc

例:
    # "部署"列を昇順、その中で"売上"列を降順にソート
    python sort_csv.py input.csv output.csv --key 部署 --key 売上:desc

    # 列を数値として比較したい場合は :num を付ける (例: 売上:desc:num)
    python sort_csv.py input.csv output.csv --key 売上:desc:num

    # ヘッダーが無いCSVで、列番号(0始まり)を指定する場合
    python sort_csv.py input.csv output.csv --key 0 --key 2:desc --no-header

    # 文字コードやCSVの区切り文字を指定する場合
    python sort_csv.py input.csv output.csv --key 列名1 --encoding cp932 --delimiter ";"

【文字コードの既定動作】
    入力: utf-8-sig  … BOM付きUTF-8のBOMを自動で取り除いて読み込みます(BOMなしUTF-8もそのまま読めます)
    出力: utf-8      … BOMなしUTF-8で書き出します
    変更したい場合は --encoding / --output-encoding を使用してください。

【--key の書式】
    列名 または 列番号 [ : asc|desc ] [ : str|num ]
    - asc/desc を省略すると昇順(asc)
    - str/num を省略すると文字列として比較(str)。数値列は num を付けると数値として正しくソートされます。
    --key は複数回指定でき、先に指定したものが優先される主キーになります。

標準ライブラリ(csv, operator)のみを使用しているため、pandasのインストールは不要です。
"""

import argparse
import csv
import functools
import sys


def parse_key_spec(spec):
    """"列名:asc|desc:str|num" 形式の文字列を解析する"""
    parts = spec.split(":")
    col = parts[0]
    order = "asc"
    kind = "str"
    for extra in parts[1:]:
        extra_lower = extra.lower()
        if extra_lower in ("asc", "desc"):
            order = extra_lower
        elif extra_lower in ("str", "num"):
            kind = extra_lower
        else:
            raise ValueError(f"--key の指定が不正です: {spec!r} (asc/desc/str/num のみ指定可能)")
    return {"col": col, "order": order, "kind": kind}


def resolve_column_index(col, header, no_header):
    """列名または列番号(文字列)を実際のインデックスに変換する"""
    if no_header or header is None:
        try:
            return int(col)
        except ValueError:
            raise ValueError(
                f"--no-header 指定時の --key には列番号(0始まりの整数)を指定してください: {col!r}"
            )
    if col in header:
        return header.index(col)
    # ヘッダーに見つからない場合、数値指定として扱えるか試す
    try:
        idx = int(col)
        if 0 <= idx < len(header):
            return idx
    except ValueError:
        pass
    raise ValueError(f"{col!r} がヘッダーに見つかりません。ヘッダー: {header}")


def make_sort_value(value, kind):
    if kind == "num":
        try:
            return float(value)
        except (TypeError, ValueError):
            # 数値に変換できない場合は最小値扱いにして末尾/先頭に寄せる
            return float("-inf")
    return value if value is not None else ""


def build_comparator(keys_resolved):
    """複数キー・列ごとの昇順/降順混在に対応した比較関数を作る"""

    def compare(row_a, row_b):
        for k in keys_resolved:
            idx = k["idx"]
            va = make_sort_value(row_a[idx] if idx < len(row_a) else "", k["kind"])
            vb = make_sort_value(row_b[idx] if idx < len(row_b) else "", k["kind"])
            if va == vb:
                continue
            result = -1 if va < vb else 1
            return -result if k["order"] == "desc" else result
        return 0

    return compare


def main():
    parser = argparse.ArgumentParser(
        description="CSVファイルを指定した複数列でソートします。",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )
    parser.add_argument("input", help="入力CSVファイルのパス")
    parser.add_argument("output", help="出力CSVファイルのパス")
    parser.add_argument(
        "--key",
        action="append",
        required=True,
        dest="keys",
        metavar="列名[:asc|desc][:str|num]",
        help="ソートキーとする列。複数回指定可能(先に指定した方が優先)",
    )
    parser.add_argument(
        "--no-header",
        action="store_true",
        help="入力CSVにヘッダー行が無い場合に指定。--key には列番号(0始まり)を使用",
    )
    parser.add_argument(
        "--encoding",
        default="utf-8-sig",
        help="入力ファイルの文字コード (既定値: utf-8-sig = BOM付き/なしUTF-8のどちらも読める。"
        "日本語ExcelのCSVなら cp932)",
    )
    parser.add_argument(
        "--output-encoding",
        default="utf-8",
        help="出力ファイルの文字コード (既定値: utf-8 = BOMなし。BOM付きにしたい場合は utf-8-sig)",
    )
    parser.add_argument(
        "--delimiter",
        default=",",
        help="CSVの区切り文字 (既定値: ',')",
    )

    args = parser.parse_args()

    try:
        key_specs = [parse_key_spec(s) for s in args.keys]
    except ValueError as e:
        print(f"エラー: {e}", file=sys.stderr)
        sys.exit(1)

    try:
        with open(args.input, "r", encoding=args.encoding, newline="") as f:
            reader = csv.reader(f, delimiter=args.delimiter)
            rows = list(reader)
    except FileNotFoundError:
        print(f"エラー: 入力ファイルが見つかりません: {args.input}", file=sys.stderr)
        sys.exit(1)
    except UnicodeDecodeError as e:
        print(
            f"エラー: 文字コードの読み込みに失敗しました ({args.encoding})。"
            f" --encoding cp932 などを試してください。詳細: {e}",
            file=sys.stderr,
        )
        sys.exit(1)

    if not rows:
        print("エラー: CSVが空です。", file=sys.stderr)
        sys.exit(1)

    if args.no_header:
        header = None
        data_rows = rows
    else:
        header = rows[0]
        data_rows = rows[1:]

    try:
        keys_resolved = []
        for spec in key_specs:
            idx = resolve_column_index(spec["col"], header, args.no_header)
            keys_resolved.append({"idx": idx, "order": spec["order"], "kind": spec["kind"]})
    except ValueError as e:
        print(f"エラー: {e}", file=sys.stderr)
        sys.exit(1)

    comparator = build_comparator(keys_resolved)
    data_rows.sort(key=functools.cmp_to_key(comparator))

    with open(args.output, "w", encoding=args.output_encoding, newline="") as f:
        writer = csv.writer(f, delimiter=args.delimiter)
        if header is not None:
            writer.writerow(header)
        writer.writerows(data_rows)

    print(f"完了: {len(data_rows)} 行を並べ替えて {args.output} に書き出しました。")


if __name__ == "__main__":
    main()

指定した行を抽出

# -*- coding: utf-8 -*-
"""
BOM付きUTF-8のCSVから、行番号リストのテキストファイルで指定した行だけを
抜き出し、BOMなしUTF-8のCSVとして書き出す。

使い方(コマンドプロンプト / PowerShell):
    python extract_rows.py 入力.csv 出力.csv 行番号リスト.txt

行番号リスト.txt は1行に1つ数字を並べたテキスト。
  例)
      2
      5
      10
  "10-20" のような範囲指定や、"2,5,10" のようなカンマ区切りも書ける。
  空行と # から始まる行は無視する。
  文字コードは UTF-8(BOM有無どちらでも可)/ シフトJIS 自動判定。

オプション:
    --no-header   ヘッダー行を持たないCSVとして扱う(先頭行から1で数える)
    --lf          出力の改行コードをLFにする(既定はCRLF)
"""

import argparse
import csv
import sys


def read_row_numbers(path):
    """行番号リストのテキストファイルを読み、行番号の集合を返す。"""
    text = None
    for enc in ("utf-8-sig", "cp932"):
        try:
            with open(path, "r", encoding=enc) as f:
                text = f.read()
            break
        except UnicodeDecodeError:
            continue
    if text is None:
        raise SystemExit(f"行番号リストの文字コードを判別できません: {path}")

    rows = set()
    for lineno, line in enumerate(text.splitlines(), start=1):
        line = line.split("#", 1)[0].strip()
        if not line:
            continue
        for part in line.replace("\t", ",").split(","):
            part = part.strip()
            if not part:
                continue
            try:
                if "-" in part:
                    start, end = part.split("-", 1)
                    rows.update(range(int(start), int(end) + 1))
                else:
                    rows.add(int(part))
            except ValueError:
                raise SystemExit(
                    f"行番号リストの {lineno} 行目を数値として読めません: {part!r}"
                )
    return rows


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("src", help="入力CSV(BOM付きUTF-8)")
    parser.add_argument("dst", help="出力CSV(BOMなしUTF-8)")
    parser.add_argument("list", help="抽出する行番号を並べたテキストファイル")
    parser.add_argument("--no-header", action="store_true",
                        help="ヘッダー行を持たないCSVとして扱う")
    parser.add_argument("--lf", action="store_true",
                        help="改行コードをLFにする(既定はCRLF)")
    args = parser.parse_args()

    targets = read_row_numbers(args.list)
    if not targets:
        raise SystemExit("行番号リストに有効な行番号がありません。")

    newline_code = "\n" if args.lf else "\r\n"

    # encoding="utf-8-sig" で読むとBOMは自動的に取り除かれる
    # encoding="utf-8"     で書くとBOMは付かない
    # newline="" はcsvモジュールを使うときのお約束(改行の二重化を防ぐ)
    with open(args.src, "r", encoding="utf-8-sig", newline="") as f_in, \
         open(args.dst, "w", encoding="utf-8", newline="") as f_out:

        reader = csv.reader(f_in)
        writer = csv.writer(f_out, lineterminator=newline_code)

        if not args.no_header:
            try:
                writer.writerow(next(reader))
            except StopIteration:
                raise SystemExit("入力ファイルが空です。")

        found = set()
        total = 0
        for i, row in enumerate(reader, start=1):
            total = i
            if i in targets:
                writer.writerow(row)
                found.add(i)

    print(f"指定 {len(targets)} 行 / 抽出 {len(found)} 行 -> {args.dst}")

    missing = sorted(targets - found)
    if missing:
        head = ", ".join(str(n) for n in missing[:10])
        more = f" ほか{len(missing) - 10}" if len(missing) > 10 else ""
        print(f"注意: データ行は {total} 行しかないため、"
              f"見つからなかった行番号があります: {head}{more}",
              file=sys.stderr)


if __name__ == "__main__":
    main()
# -*- coding: utf-8 -*-
"""
CSV セット行抽出スクリプト(Windows / pandas)

【処理内容】
  KEY_COLS で指定した 3 列の値がすべて同じ行どうしをグループとみなし、
  そのグループ内で FLAG_COL の値が VAL_KEEP("2") の行と VAL_DROP("6") の行を
  出現順に 1 対 1 でペアにする。
  ペアが成立した分だけ "6" の行を削除し、"2" の行はそのまま残す。

  ・2 だけの行         → そのまま残る
  ・6 だけの行         → そのまま残る
  ・ペアが余った 6     → そのまま残る(1 対 1 でペアにするため)
  ・2 でも 6 でもない行 → そのまま残る
  ・行の並び順は元の CSV のまま

【使い方】
  1) 下の「設定」を自分の CSV に合わせて書き換える
  2) コマンドプロンプトで:
         python filter_pairs.py
     引数で指定することもできる:
         python filter_pairs.py 入力.csv 出力.csv
"""

import sys
import os
import pandas as pd

# ============================ 設定 ============================

# 入力・出力ファイル(コマンドライン引数があればそちらが優先)
INPUT_FILE = r"C:\data\input.csv"
OUTPUT_FILE = r"C:\data\output.csv"

# 同一かどうかを判定する 3 つの列(ヘッダー名。HAS_HEADER=False なら 0 始まりの列番号)
KEY_COLS = ["列A", "列B", "列C"]

# 2 / 6 が入っている列
FLAG_COL = "区分"

# 残す値 / セット成立時に削除する値
VAL_KEEP = "2"
VAL_DROP = "6"

# CSV に見出し行があるか
HAS_HEADER = True

# 文字コード(None なら自動判定)。固定したい場合は "cp932" や "utf-8-sig" を直接指定
ENCODING = None

# 削除した 6 の行を別ファイルに保存したい場合はパスを指定(不要なら None)
REMOVED_FILE = None   # 例: r"C:\data\removed.csv"

# ==============================================================


def detect_encoding(path):
    """UTF-8(BOM) → UTF-8 → cp932 の順に試して、読めた文字コードを返す"""
    for enc in ("utf-8-sig", "utf-8", "cp932"):
        try:
            with open(path, "r", encoding=enc) as f:
                f.read()
            return enc
        except UnicodeDecodeError:
            continue
    raise UnicodeDecodeError(
        "auto", b"", 0, 1,
        "文字コードを自動判定できませんでした。ENCODING に直接指定してください。"
    )


def norm(series):
    """比較用に文字列化して前後の空白を除去('2' と 2 と ' 2 ' を同じ扱いにする)"""
    return series.astype(str).str.strip()


def main():
    in_path = sys.argv[1] if len(sys.argv) > 1 else INPUT_FILE
    out_path = sys.argv[2] if len(sys.argv) > 2 else OUTPUT_FILE

    if not os.path.exists(in_path):
        print(f"[エラー] 入力ファイルが見つかりません: {in_path}")
        sys.exit(1)

    enc = ENCODING or detect_encoding(in_path)
    print(f"文字コード: {enc}")

    # すべて文字列として読み込む(先頭ゼロや書式を壊さないため)
    df = pd.read_csv(
        in_path,
        dtype=str,
        keep_default_na=False,
        header=0 if HAS_HEADER else None,
        encoding=enc,
    )

    # 列指定のチェック
    for col in list(KEY_COLS) + [FLAG_COL]:
        if col not in df.columns:
            print(f"[エラー] 列が見つかりません: {col}")
            print(f"        実際の列: {list(df.columns)}")
            sys.exit(1)

    # --- 判定用の正規化した一時列を作る ---
    work = pd.DataFrame(index=df.index)
    for i, col in enumerate(KEY_COLS):
        work[f"_k{i}"] = norm(df[col])
    work["_flag"] = norm(df[FLAG_COL])

    key_cols_tmp = [f"_k{i}" for i in range(len(KEY_COLS))]

    # 削除対象の行(インデックス)を集める
    drop_index = []

    for _, grp in work.groupby(key_cols_tmp, sort=False, dropna=False):
        keep_rows = grp.index[grp["_flag"] == VAL_KEEP]   # 2 の行(出現順)
        drop_rows = grp.index[grp["_flag"] == VAL_DROP]   # 6 の行(出現順)
        n_pair = min(len(keep_rows), len(drop_rows))      # 1 対 1 でペアにできる数
        if n_pair > 0:
            # ペアになった分だけ 6 の行を削除(余った 6 は残す)
            drop_index.extend(drop_rows[:n_pair])

    result = df.drop(index=drop_index)

    # --- 出力 ---
    result.to_csv(out_path, index=False, header=HAS_HEADER, encoding=enc)

    if REMOVED_FILE and drop_index:
        df.loc[drop_index].to_csv(
            REMOVED_FILE, index=False, header=HAS_HEADER, encoding=enc
        )

    print(f"入力行数  : {len(df)}")
    print(f"削除行数  : {len(drop_index)}  (セット成立した {VAL_DROP} の行)")
    print(f"出力行数  : {len(result)}")
    print(f"出力先    : {out_path}")


if __name__ == "__main__":
    main()

CSV 2列置換

# -*- coding: utf-8 -*-
"""
CSV の 2列目・3列目のペアを、変換表にしたがって置換するスクリプト。

使い方(どちらでもOK):
  1) 下の「設定」の3つのパスを書き換えて、ダブルクリック / python replace_pairs.py
  2) コマンドラインから:
       python replace_pairs.py 入力.csv 変換表.csv 出力.csv

変換表 (mapping.csv) の形式: ヘッダー無し・4列
    旧2列目,旧3列目,新2列目,新3列目
  例:
    3310245,09021234,3589134,52384971
"""

import csv
import os
import sys

# ============================ 設定 ============================
INPUT_CSV   = r"C:\work\input.csv"     # 置換対象のCSV(ヘッダー無し)
MAPPING_CSV = r"C:\work\mapping.csv"   # 変換表(4列・ヘッダー無し)
OUTPUT_CSV  = r"C:\work\output.csv"    # 出力先(新規作成 / 上書き)

ENCODING     = "utf-8"   # 入出力の文字コード(BOM無しUTF-8)
NEWLINE      = "\r\n"    # 出力の改行コード(CRLF)
KEY_COL_A    = 1         # 2列目 → 0始まりで 1
KEY_COL_B    = 2         # 3列目 → 0始まりで 2
STRIP_FIELDS = False     # True にするとキー比較時に前後の空白を無視
# ==============================================================


def load_mapping(path):
    """変換表を読み込んで {(旧2, 旧3): (新2, 新3)} を返す。"""
    mapping = {}
    with open(path, "r", encoding="utf-8-sig", newline="") as f:
        for lineno, row in enumerate(csv.reader(f), start=1):
            if not row or all(c.strip() == "" for c in row):
                continue  # 空行はスキップ
            if len(row) < 4:
                raise ValueError(
                    "変換表 %s の %d 行目の列数が %d です(4列必要): %r"
                    % (path, lineno, len(row), row)
                )
            old_a, old_b, new_a, new_b = (c.strip() for c in row[:4])
            key = (old_a, old_b)
            if key in mapping and mapping[key] != (new_a, new_b):
                raise ValueError(
                    "変換表 %s の %d 行目: キー %s,%s が重複していて変換先が違います"
                    % (path, lineno, old_a, old_b)
                )
            mapping[key] = (new_a, new_b)
    if not mapping:
        raise ValueError("変換表 %s に有効な行がありません" % path)
    return mapping


def main():
    args = sys.argv[1:]
    if len(args) == 3:
        input_csv, mapping_csv, output_csv = args
    elif len(args) == 0:
        input_csv, mapping_csv, output_csv = INPUT_CSV, MAPPING_CSV, OUTPUT_CSV
    else:
        print("使い方: python replace_pairs.py [入力.csv 変換表.csv 出力.csv]")
        return 2

    for label, path in (("入力CSV", input_csv), ("変換表", mapping_csv)):
        if not os.path.isfile(path):
            print("エラー: %s が見つかりません -> %s" % (label, path))
            return 1

    if os.path.abspath(input_csv) == os.path.abspath(output_csv):
        print("エラー: 入力CSVと出力CSVが同じファイルです。別名にしてください。")
        return 1

    mapping = load_mapping(mapping_csv)
    hit_count = {key: 0 for key in mapping}

    total_rows = 0
    replaced_rows = 0
    short_rows = []  # 列数が足りない行

    with open(input_csv, "r", encoding=ENCODING, newline="") as fin, \
         open(output_csv, "w", encoding=ENCODING, newline="") as fout:
        reader = csv.reader(fin)
        writer = csv.writer(fout, lineterminator=NEWLINE)

        for lineno, row in enumerate(reader, start=1):
            if not row:
                writer.writerow(row)
                continue
            total_rows += 1

            if len(row) <= KEY_COL_B:
                short_rows.append(lineno)
                writer.writerow(row)
                continue

            a, b = row[KEY_COL_A], row[KEY_COL_B]
            key = (a.strip(), b.strip()) if STRIP_FIELDS else (a, b)
            new_pair = mapping.get(key)
            if new_pair is not None:
                row[KEY_COL_A], row[KEY_COL_B] = new_pair
                replaced_rows += 1
                hit_count[key] += 1

            writer.writerow(row)

    # ------------------------- 結果レポート -------------------------
    unused = [k for k, n in hit_count.items() if n == 0]

    print("完了しました。")
    print("  出力ファイル : %s" % os.path.abspath(output_csv))
    print("  読み込み行数 : %d" % total_rows)
    print("  置換した行数 : %d" % replaced_rows)
    print("  変換表ペア数 : %d (うち未使用 %d)" % (len(mapping), len(unused)))

    if short_rows:
        head = ", ".join(str(n) for n in short_rows[:10])
        more = " ほか" if len(short_rows) > 10 else ""
        print("  ※ 列数が3未満のため素通しした行: %d 行 (行番号: %s%s)"
              % (len(short_rows), head, more))

    if unused:
        print("  ※ 一度も一致しなかった変換表のペア: %d 件" % len(unused))
        for a, b in unused[:20]:
            print("       %s,%s" % (a, b))
        if len(unused) > 20:
            print("       ... 他 %d 件" % (len(unused) - 20))

    return 0


if __name__ == "__main__":
    try:
        code = main()
    except Exception as e:
        print("エラー: %s" % e)
        code = 1
    if not sys.argv[1:]:
        # ダブルクリック起動時に画面が消えないように
        try:
            input("Enterキーで終了します...")
        except EOFError:
            pass
    sys.exit(code)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
ペアCSV検証スクリプト (Windows / Python 3.8+)

■ 概要
  ペアCSV(2行1セット)を検証し、すべてのペアがデータCSVに存在するかを確認します。
  結果はテキストログファイルに出力します。

■ 使い方
  1) コマンドライン引数で指定する場合
       python verify_pairs.py ペアCSV データCSV [ログファイル]
     例: python verify_pairs.py pair.csv data.csv result.log

  2) 引数を省略する場合
     下の【設定】の PAIR_CSV / DATA_CSV / LOG_FILE を書き換えて、
     ダブルクリック or `python verify_pairs.py` で実行してください。

■ 終了コード
  0 = エラーなし / 1 = エラーあり / 2 = 実行時エラー(ファイルが無い等)
"""

import csv
import os
import re
import sys
from datetime import datetime

# ============================================================================
# 【設定】コマンドライン引数を指定しない場合は、ここを書き換えてください
# ============================================================================
PAIR_CSV = r"pair.csv"          # 検証対象のペアCSV(2行1セット)
DATA_CSV = r"data.csv"          # 定義データCSV(4列)
LOG_FILE = r"verify_result.log"  # 出力するログファイル

# --- 入出力の文字コード -------------------------------------------------------
# 入力: UTF-8(BOM無し)想定。"utf-8-sig" にしておくとBOM付きでも読めます。
INPUT_ENCODING = "utf-8-sig"
# 出力: メモ帳・Excelで文字化けしないようBOM付きUTF-8。改行はCRLF。
OUTPUT_ENCODING = "utf-8-sig"
OUTPUT_NEWLINE = "\r\n"

# --- 列位置(0始まり)--------------------------------------------------------
COL_FLAG = 0      # 1列目: 0 / 1
COL_VAL1 = 1      # 2列目: 値(末尾に半角スペースあり → トリムして比較)
COL_VAL2 = 2      # 3列目: 値
COLS_SAME = (3, 4, 5, 6)  # 4〜7列目: ペア内で同一であるべき列
COL_SETNO = 7     # 8列目: セットNo(半角数字4桁)

PAIR_MIN_COLS = 8   # ペアCSVに必要な最小列数
DATA_COLS = 4       # データCSVの列数

# --- トリム対象列(0始まり)---------------------------------------------------
# 仕様上トリムが必要なのはペアCSVの2列目のみ。
# 他の列もトリムしたい場合は、下のタプルに列番号を追加してください。
PAIR_TRIM_COLS = (COL_VAL1,)
DATA_TRIM_COLS = ()   # 例: 全列トリムするなら (0, 1, 2, 3)

# --- ログの詳細出力件数の上限(0 = 無制限)------------------------------------
MAX_DETAIL = 0

SETNO_PATTERN = re.compile(r"^[0-9]{4}$")

# ============================================================================


class Report(object):
    """ログ本文を組み立てるヘルパ"""

    def __init__(self):
        self.lines = []

    def add(self, text=""):
        self.lines.append(text)

    def section(self, title):
        self.add("")
        self.add("-" * 78)
        self.add(title)
        self.add("-" * 78)

    def details(self, items):
        """詳細行を出力(MAX_DETAIL で打ち切り)"""
        if not items:
            self.add("  (該当なし)")
            return
        shown = items if MAX_DETAIL <= 0 else items[:MAX_DETAIL]
        for item in shown:
            self.add("  " + item)
        rest = len(items) - len(shown)
        if rest > 0:
            self.add("  ... 他 %d 件(MAX_DETAIL で省略)" % rest)

    def text(self):
        return OUTPUT_NEWLINE.join(self.lines) + OUTPUT_NEWLINE


def read_csv(path, encoding):
    """CSVを [(物理行番号, [値, ...]), ...] で返す。空行はスキップ。"""
    rows = []
    with open(path, "r", encoding=encoding, newline="") as f:
        reader = csv.reader(f)
        for fields in reader:
            if not fields or all(v.strip() == "" for v in fields):
                continue
            rows.append((reader.line_num, fields))
    return rows


def trim(fields, trim_cols):
    """指定列の前後空白を除去したリストを返す"""
    out = list(fields)
    for i in trim_cols:
        if i < len(out):
            out[i] = out[i].strip()
    return out


def load_data_csv(path, report):
    """
    データCSVを読み込み、(4値のタプル) -> [行番号, ...] の辞書を返す。
    戻り値: (キー辞書, 行数, 形式エラー件数)
    """
    rows = read_csv(path, INPUT_ENCODING)
    keys = {}
    bad_cols = []

    for lineno, fields in rows:
        if len(fields) != DATA_COLS:
            bad_cols.append(
                "行 %d: 列数が %d 列です(期待値 %d 列): %s"
                % (lineno, len(fields), DATA_COLS, ",".join(fields))
            )
            if len(fields) < DATA_COLS:
                continue
        f = trim(fields, DATA_TRIM_COLS)
        key = (f[0], f[1], f[2], f[3])
        keys.setdefault(key, []).append(lineno)

    if bad_cols:
        report.section("[エラー] データCSV: 列数不正")
        report.add("件数: %d 件" % len(bad_cols))
        report.details(bad_cols)

    return keys, len(rows), len(bad_cols)


def main():
    # ---- パス決定(引数優先、無ければ設定値)--------------------------------
    args = sys.argv[1:]
    pair_path = args[0] if len(args) >= 1 else PAIR_CSV
    data_path = args[1] if len(args) >= 2 else DATA_CSV
    log_path = args[2] if len(args) >= 3 else LOG_FILE

    for p, label in ((pair_path, "ペアCSV"), (data_path, "データCSV")):
        if not os.path.isfile(p):
            sys.stderr.write("%s が見つかりません: %s\n" % (label, p))
            return 2

    started = datetime.now()
    report = Report()
    report.add("=" * 78)
    report.add("ペアCSV検証結果")
    report.add("=" * 78)
    report.add("実行日時      : %s" % started.strftime("%Y-%m-%d %H:%M:%S"))
    report.add("ペアCSV       : %s" % os.path.abspath(pair_path))
    report.add("データCSV     : %s" % os.path.abspath(data_path))
    report.add("ログファイル  : %s" % os.path.abspath(log_path))

    # ---- データCSV読み込み --------------------------------------------------
    data_keys, data_row_count, data_col_errors = load_data_csv(data_path, report)

    # データCSVの重複行
    data_dups = []
    for key, linenos in data_keys.items():
        if len(linenos) > 1:
            data_dups.append(
                "%s  (行 %s)" % (",".join(key), ", ".join(str(n) for n in linenos))
            )

    # ---- ペアCSV読み込み ----------------------------------------------------
    pair_rows = read_csv(pair_path, INPUT_ENCODING)

    col_errors = []      # 列数不足
    groups = {}          # セットNo -> [(行番号, フィールド), ...]
    for lineno, fields in pair_rows:
        if len(fields) < PAIR_MIN_COLS:
            col_errors.append(
                "行 %d: 列数が %d 列です(最低 %d 列必要): %s"
                % (lineno, len(fields), PAIR_MIN_COLS, ",".join(fields))
            )
            continue
        f = trim(fields, PAIR_TRIM_COLS)
        groups.setdefault(f[COL_SETNO], []).append((lineno, f))

    # ---- ペアCSV自体の整合性チェック ---------------------------------------
    setno_format_errors = []
    group_size_errors = []
    flag_errors = []
    same_col_errors = []

    valid_pairs = []   # (セットNo, 行0情報, 行1情報)

    for setno, members in groups.items():
        linenos = ", ".join(str(n) for n, _ in members)

        # セットNo書式(半角数字4桁・先頭ゼロパディング)
        if not SETNO_PATTERN.match(setno):
            setno_format_errors.append(
                "セットNo '%s': 半角数字4桁ではありません (行 %s)" % (setno, linenos)
            )

        # 1セット = 2行
        if len(members) != 2:
            group_size_errors.append(
                "セットNo '%s': %d 行あります(期待値 2 行) (行 %s)"
                % (setno, len(members), linenos)
            )
            continue

        (ln_a, fa), (ln_b, fb) = members

        # 1列目は 0 と 1 の組合せ
        flags = (fa[COL_FLAG], fb[COL_FLAG])
        if set(flags) != set(("0", "1")):
            flag_errors.append(
                "セットNo '%s': 1列目が ('%s','%s') です(0と1の組合せが必要) (行 %s)"
                % (setno, flags[0], flags[1], linenos)
            )
            continue

        # 4〜7列目がペア内で同一
        diff = []
        for i in COLS_SAME:
            if fa[i] != fb[i]:
                diff.append("%d列目 '%s' != '%s'" % (i + 1, fa[i], fb[i]))
        if diff:
            same_col_errors.append(
                "セットNo '%s': %s (行 %s)" % (setno, " / ".join(diff), linenos)
            )

        row0, row1 = (fa, fb) if fa[COL_FLAG] == "0" else (fb, fa)
        ln0, ln1 = (ln_a, ln_b) if fa[COL_FLAG] == "0" else (ln_b, ln_a)
        valid_pairs.append((setno, ln0, row0, ln1, row1))

    # ---- ペアCSV → データCSV 照合 ------------------------------------------
    not_found = []
    matched_data_keys = set()
    matched_count = 0

    for setno, ln0, row0, ln1, row1 in valid_pairs:
        key = (row0[COL_VAL1], row0[COL_VAL2], row1[COL_VAL1], row1[COL_VAL2])
        if key in data_keys:
            matched_count += 1
            matched_data_keys.add(key)
        else:
            not_found.append(
                "セットNo '%s' (行 %d, %d): [%s]"
                % (setno, ln0, ln1, ", ".join("'%s'" % v for v in key))
            )

    # データCSVの未使用行
    unused = []
    for key, linenos in sorted(data_keys.items(), key=lambda kv: kv[1][0]):
        if key not in matched_data_keys:
            unused.append(
                "行 %s: %s" % (", ".join(str(n) for n in linenos), ",".join(key))
            )

    # ========================================================================
    # ログ出力
    # ========================================================================
    error_count = (
        data_col_errors
        + len(col_errors)
        + len(setno_format_errors)
        + len(group_size_errors)
        + len(flag_errors)
        + len(same_col_errors)
        + len(not_found)
    )

    report.section("■ サマリ")
    report.add("データCSV  読込行数        : %d 行" % data_row_count)
    report.add("データCSV  ユニークキー数  : %d 件" % len(data_keys))
    report.add("ペアCSV    読込行数        : %d 行" % len(pair_rows))
    report.add("ペアCSV    セットNo数      : %d 件" % len(groups))
    report.add("ペアCSV    検証対象ペア数  : %d 件" % len(valid_pairs))
    report.add("")
    report.add("[照合結果]")
    report.add("  データCSVに存在した      : %d 件" % matched_count)
    report.add("  データCSVに存在しない    : %d 件" % len(not_found))
    report.add("")
    report.add("[ペアCSV整合性]")
    report.add("  列数不足                 : %d 件" % len(col_errors))
    report.add("  セットNo書式不正         : %d 件" % len(setno_format_errors))
    report.add("  ペア行数不正(≠2行)       : %d 件" % len(group_size_errors))
    report.add("  1列目0/1の組合せ不正     : %d 件" % len(flag_errors))
    report.add("  4〜7列目の不一致         : %d 件" % len(same_col_errors))
    report.add("")
    report.add("[データCSV]")
    report.add("  列数不正                 : %d 件" % data_col_errors)
    report.add("  重複行                   : %d 件(参考)" % len(data_dups))
    report.add("  未使用行                 : %d 件(参考)" % len(unused))
    report.add("")
    report.add("判定: %s" % ("OK(エラーなし)" if error_count == 0 else "NG(エラー %d 件)" % error_count))

    report.section("[エラー] データCSVに存在しないペア")
    report.add("件数: %d 件" % len(not_found))
    report.add("形式: セットNo (行番号) : [1列目0の行の2列目, 同3列目, 1列目1の行の2列目, 同3列目]")
    report.details(not_found)

    report.section("[エラー] ペアCSV: 列数不足")
    report.add("件数: %d 件" % len(col_errors))
    report.details(col_errors)

    report.section("[エラー] ペアCSV: セットNo書式不正(半角数字4桁)")
    report.add("件数: %d 件" % len(setno_format_errors))
    report.details(setno_format_errors)

    report.section("[エラー] ペアCSV: セットNoごとの行数が2行でない")
    report.add("件数: %d 件" % len(group_size_errors))
    report.details(group_size_errors)

    report.section("[エラー] ペアCSV: 1列目が0と1の組合せでない")
    report.add("件数: %d 件" % len(flag_errors))
    report.details(flag_errors)

    report.section("[エラー] ペアCSV: 4〜7列目がペア内で不一致")
    report.add("件数: %d 件" % len(same_col_errors))
    report.details(same_col_errors)

    report.section("[参考] データCSV: 重複行")
    report.add("件数: %d 件" % len(data_dups))
    report.details(data_dups)

    report.section("[参考] データCSV: どのペアからも参照されなかった行")
    report.add("件数: %d 件" % len(unused))
    report.details(unused)

    report.add("")
    report.add("=" * 78)
    report.add("処理時間: %.2f 秒" % (datetime.now() - started).total_seconds())
    report.add("=" * 78)

    with open(log_path, "w", encoding=OUTPUT_ENCODING, newline="") as f:
        f.write(report.text())

    # ---- コンソールにも要約を表示 -------------------------------------------
    print("検証完了: %s" % ("OK(エラーなし)" if error_count == 0 else "NG(エラー %d 件)" % error_count))
    print("  ペア数 %d / 一致 %d / 不一致 %d" % (len(valid_pairs), matched_count, len(not_found)))
    print("  ログ: %s" % os.path.abspath(log_path))

    return 0 if error_count == 0 else 1


if __name__ == "__main__":
    try:
        sys.exit(main())
    except Exception as e:  # noqa: BLE001
        sys.stderr.write("実行時エラー: %s\n" % e)
        sys.exit(2)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?