秀丸エディタで「固定長→区切り(TSV)」:桁数で分割してタブ区切りにする(昔の帳票データに刺さる)

昔の帳票データ、ログ、ホストから落ちてきたテキストって「固定長(桁位置で列が決まる)」が多い。

例(固定長):

00012345TANAKA      TOKYO     0002500
00012346SUZUKI      NAGOYA    0010000

こういうのを TSV(タブ区切り) にできると、ExcelでもDBでも一気に扱いやすくなる。

この記事は、秀丸で「選択範囲(または全文)」を 列幅指定で分割→TSV化 するマクロを用意する。


できること

  • 固定長テキストを、指定した列幅(桁数)で分割して TSV にする
  • 選択範囲だけでも、全文でもOK
  • 各フィールドの前後空白を削る(任意)
  • 空行をスキップ(任意)
  • 「最後の列は残り全部」みたいな指定ができる(幅=0)

重要:固定長は「文字数」か「バイト数」か

固定長の定義には2種類ある。

  • 文字数(見た目の桁):英数字中心のデータで多い
  • バイト数(Shift-JIS等のバイト幅):日本語混在の帳票で多い

この記事のマクロは 文字数(文字位置)基準
英数字中心ならほぼ問題なし。

日本語混在で「バイト幅で切られている」データは、文字数基準だとズレることがある。
その場合は、後半の「バイト幅版(Python)」を使うのが安全。


1) マクロ(文字数基準):固定長→TSV

ファイル名:fixed_to_tsv.mac

/*
  fixed_to_tsv.mac
  固定長(文字数基準)を列幅指定で分割してTSVへ変換する。

  列幅指定の例:
    8,10,10,7,0
      - 最後の 0 は「残り全部」
    8 10 10 7 0
      - スペース区切りもOK

  対象:
    - 選択範囲があれば選択だけ
    - 選択が無ければ全文

  オプション:
    - 空行スキップ
    - 各フィールドの前後空白削除(右だけ/両方)
*/

#scope = val(input("対象: 1=選択(無ければ全文) / 2=全文", "1"));
$spec  = input("列幅(例: 8,10,10,7,0 ※0=残り全部)", "8,10,10,7,0");
#trim  = val(input("空白削除: 0=なし / 1=右だけ / 2=両方", "1"));
#skip  = val(input("空行スキップ: 0=しない / 1=する", "1"));

$CR  = char(13);
$LF  = char(10);
$TAB = char(9);
$SP  = " ";

if (#scope == 2 || !selecting) {
  selectall;
}

// 選択文字列取得
#sx = seltopx;
#sy = seltopy;
$s  = gettext(#sx, #sy, selendx, selendy, 1);

// 改行をLFへ寄せる
$s2 = strreplace($s, $CR + $LF, $LF);
$s2 = strreplace($s2, $CR, $LF);

#slen = strlen($s2);
#start = 0;
$out = "";

// 文字が数字か判定する用(0-9)
function IsDigit($c) {
  if ($c >= "0" && $c <= "9") return 1;
  return 0;
}

// 右空白削除
function RTrim($t) {
  while (strlen($t) > 0) {
    $last = midstr($t, strlen($t), 1);
    if ($last == $SP || $last == $TAB) {
      $t = leftstr($t, strlen($t) - 1);
    } else {
      break;
    }
  }
  return $t;
}

// 左空白削除
function LTrim($t) {
  while (strlen($t) > 0) {
    $first = midstr($t, 1, 1);
    if ($first == $SP || $first == $TAB) {
      $t = midstr($t, 2, strlen($t) - 1);
    } else {
      break;
    }
  }
  return $t;
}

// 1行ずつ処理
while (1) {
  #p = strstr($s2, $LF, #start);
  if (#p < 0) {
    $line = midstr($s2, #start + 1, #slen - #start);
    #last = 1;
  } else {
    $line = midstr($s2, #start + 1, #p - #start);
    #last = 0;
  }

  // 空行スキップ(空白だけの行もスキップ扱いにする)
  if (#skip == 1) {
    $tmp = $line;
    $tmp = RTrim($tmp);
    $tmp = LTrim($tmp);
    if ($tmp == "") {
      // そのまま出力しない
      goto NEXTLINE;
    }
  }

  // ここから固定長分割
  #pos = 0;
  $outLine = "";
  #field = 0;

  // specを走査して幅トークンを拾う(毎行パース:単純だけど堅い)
  #i = 0;
  #specLen = strlen($spec);
  $token = "";

  while (#i <= #specLen) {
    if (#i == #specLen) {
      $ch = ","; // 終端フラッシュ用
    } else {
      $ch = midstr($spec, #i + 1, 1);
    }

    if (IsDigit($ch)) {
      $token = $token + $ch;
    } else {
      if ($token != "") {
        #w = val($token);
        $token = "";

        #field = #field + 1;
        if (#field > 1) $outLine = $outLine + $TAB;

        if (#w == 0) {
          // 残り全部
          $part = midstr($line, #pos + 1, 999999);
          #pos = 999999;
        } else {
          $part = midstr($line, #pos + 1, #w);
          #pos = #pos + #w;
        }

        // 空白削除
        if (#trim == 1) {
          $part = RTrim($part);
        } else if (#trim == 2) {
          $part = RTrim($part);
          $part = LTrim($part);
        }

        $outLine = $outLine + $part;
      }
    }

    #i = #i + 1;
  }

  $out = $out + $outLine;

  if (#last) break;
  $out = $out + $CR + $LF;

NEXTLINE:
  if (#last) break;
  #start = #p + 1;
}

// 置換(選択をTSVにする)
delete;
moveto #sx, #sy;
insert $out;

message "固定長→TSV 完了(文字数基準)";

2) 使い方

例:列幅 8,12,10,7,0 に分割

  1. 変換したい範囲を選択(無ければ全文)
  2. マクロ実行
  3. 列幅に 8,12,10,7,0 を入力
  4. 空白削除は基本 1(右だけ) が無難
  5. 出力がTSVになる(列はタブ区切り)

3) 変換例(イメージ)

固定長:

00012345TANAKA      TOKYO     0002500
00012346SUZUKI      NAGOYA    0010000

列幅:8,12,10,7,0

TSV(見た目はこうなる):

00012345    TANAKA      TOKYO     0002500
00012346    SUZUKI      NAGOYA    0010000

4) よくある調整ポイント

空白削除はどれがいい?

  • 0(なし):帳票の“見た目”を残したい時
  • 1(右だけ):固定長の余白を落として、列をきれいにしたい時(おすすめ)
  • 2(両方):列の左余白も消したい時(見出しや整形済みならアリ)

ヘッダ行や罫線行が邪魔

  • そこだけ選択から外す
  • 罫線行だけ削除してから実行

5) 日本語混在でズレる場合(バイト幅の固定長)

日本語が混じる帳票は「Shift-JISのバイト幅」で固定長になってることがある。
この場合、上のマクロ(文字数基準)だと境界がズレることがある。

その時は、外部で“バイト幅で切る”のが安全。
以下は Python で標準入力をバイトで切ってTSVにする最小構成。

Python(バイト幅版) filters\fixed2tsv_bytes.py

from __future__ import annotations
import sys

def parse_widths(spec: str) -> list[int]:
    # "8,10,7,0" / "8 10 7 0" どっちでもOK
    buf = ""
    out: list[int] = []
    for ch in spec:
        if ch.isdigit():
            buf += ch
        else:
            if buf:
                out.append(int(buf))
                buf = ""
    if buf:
        out.append(int(buf))
    return out

def rtrim_bytes(b: bytes) -> bytes:
    return b.rstrip(b" \t")

def main() -> None:
    # ここは帳票の実体に合わせる(cp932が多い)
    enc = sys.argv[1] if len(sys.argv) >= 2 else "cp932"
    spec = sys.argv[2] if len(sys.argv) >= 3 else "8,10,7,0"
    trim = sys.argv[3] if len(sys.argv) >= 4 else "r"  # r or none

    widths = parse_widths(spec)
    data = sys.stdin.buffer.read()

    # 改行を吸収して行へ
    text = data.decode(enc, errors="replace")
    lines = text.replace("\r\n", "\n").replace("\r", "\n").split("\n")

    out_lines: list[str] = []
    for line in lines:
        # 行を元のエンコーディングに戻してバイト切りする
        raw = line.encode(enc, errors="replace")
        pos = 0
        fields: list[str] = []
        for w in widths:
            if w == 0:
                part = raw[pos:]
                pos = len(raw)
            else:
                part = raw[pos:pos+w]
                pos += w
            if trim == "r":
                part = rtrim_bytes(part)
            fields.append(part.decode(enc, errors="replace"))
        out_lines.append("\t".join(fields))

    sys.stdout.write("\r\n".join(out_lines))

if __name__ == "__main__":
    main()

秀丸マクロ(外部呼び出し) fixed_to_tsv_bytes.mac

/*
  fixed_to_tsv_bytes.mac
  選択(無ければ全文)をPythonへ渡して、バイト幅固定長→TSVにする。
*/

#scope = val(input("対象: 1=選択(無ければ全文) / 2=全文", "1"));
$enc  = input("エンコーディング(例: cp932 / utf-8)", "cp932");
$spec = input("列幅(例: 8,10,7,0 ※0=残り)", "8,10,7,0");

if (#scope == 2 || !selecting) {
  selectall;
}

// python.exe のパスは必要ならフルパスにする
$cmd = "python.exe \"" + macrodir + "\\filters\\fixed2tsv_bytes.py\" " + $enc + " \"" + $spec + "\" r";

runex $cmd
, 1
, 5, ""   // stdin=選択
, 6, ""   // stdout=選択を置換
, 7, ""
, 1, ""
, 0
, 1
, 6       // UTF-8受け渡し(スクリプト側はcp932→UTF-8出力でもOK)
, 1
;

if (!result) {
  message "失敗。python.exeのパス / filters\\fixed2tsv_bytes.py を確認。";
  endmacro;
}

message "固定長→TSV 完了(バイト幅版)";

まとめ

  • 固定長→TSVは、昔の帳票データを現代ツールへ持ち込む最短ルート
  • 英数字中心なら「文字数基準マクロ」で十分
  • 日本語混在でズレるなら「バイト幅版(Python)」が安全

これを用意しとくと、テキストの前処理が一気に早くなる。

タイトルとURLをコピーしました