昔の帳票データ、ログ、ホストから落ちてきたテキストって「固定長(桁位置で列が決まる)」が多い。
例(固定長):
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 に分割
- 変換したい範囲を選択(無ければ全文)
- マクロ実行
- 列幅に
8,12,10,7,0を入力 - 空白削除は基本
1(右だけ)が無難 - 出力が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)」が安全
これを用意しとくと、テキストの前処理が一気に早くなる。
