目次
- コマンドプロンプト・プログラミング 序
- コマンドプロンプト・プログラミング 々
- コマンドプロンプト・プログラミング 宴
- コマンドプロンプト・プログラミング 二次会
- コマンドプロンプト・プログラミング 三次会 <- ★
- コマンドプロンプト・プログラミング 壮行会
Lua と nkf をつなぐ会。
CP932/UTF-8 相互変換
パイプによる変換
nkf に -w
を与えると UTF-8 を吐き出してくれる。
-L
で改行コード変換。
-
-Lw
:Windows(CRLF) -
-Lm
:Mac(CR)※ 最新 Mac は LF -
-Lu
:Unix(LF)
--guess
で文字コードを推測してくれる。
> echo あいうえお | nkf --guess
Shift_JIS (CRLF)
> echo あいうえお | nkf -w
縺ゅ>縺・∴縺・
> echo あいうえお | nkf -w | nkf --guess
UTF-8 (CRLF)
> echo あいうえお | nkf -wLu | nkf --guess
UTF-8 (LF)
> echo あいうえお | nkf -wLu | nkf
あいうえお
> echo あいうえお | nkf -wLu | nkf | nkf --guess
Shift_JIS (LF)
> echo あいうえお | nkf -wLu | nkf -Lw | nkf --guess
Shift_JIS (CRLF)
バッチファイルでラップする
-w
と --oc=UTF-8
は等価。
@echo off
nkf.exe --ic=CP932 --oc=UTF-8 -Lu
exit /b
@echo off
nkf.exe --ic=UTF-8 --oc=CP932 -Lw
exit /b
> echo あいうえお | win2unix | unix2win
あいうえお
Lua から nkf を呼ぶ
os.execute([command])
Lua で外部コマンドを呼ぶときにこれを使うが、
返り値は多値にて boolean, 'exit'/'signal', errorCode
が返る。
本当に欲しいであろう値は標準出力として表示される。
> os.execute()
true -- 外部コマンド実行可能
> os.execute("echo あいうえお| nkf --guess")
Shift_JIS (CRLF) -- 標準出力
true exit 0 -- 返り値(成功、exit、エラーコード)
この標準出力を Lua 内部で拾いたい。
io.popen(prog[, mode])
これは別プロセスを作る関数で、返り値はファイルハンドルである。
パイプを作る関数と言った方が正しいか。
> file = io.popen("nkf.exe --guess", "w") -- 書き込みモード
> file:write("あいうえお")
file (000007fefe412b10)
> file:close()
Shif_JIS
true exit 0
>
> file = io.popen("nkf.exe --guess", "w"):write("あいうえお\n")
> file:close()
Shift_JIS (CRLF)
true exit 0
(注意:Lua はメソッドチェーンを :
で書く)
os.execute
は io.popen
で実装されていると予想できる。
ここでふと気づく。
結局どうやって nkf の出力を拾えばいいのかと。
というわけで、今度は読み込みモードを試してみる。
読み込みモードは "r"
を指定するが、デフォなので無指定でもよい。
> file = io.popen("echo あいうえお") -- 読み込みモード
> reply = file:read("a") -- 全読み込み
> reply
あいうえお
> file:close()
true exit 0
>
> file = io.popen("echo あいうえお")
> reply = file:read("l") -- 行読み込み
> reply
あいうえお
> file:close()
true exit 0
これでパイプからの出力を拾うことができた。
だが、両モード混在はできないようだ。
> io.popen("nkf.exe --guess", "rw")
nil nkf.exe --guess: Invalid argument 22
というわけで、
「nkf に文字列をパイプで渡すコマンドライン」を
読み込みモードで呼び出すこととなる。
> file = io.popen("echo あいうえお| nkf.exe -wLu")
> reply = file:read("a")
> reply
縺ゅ>縺・∴縺・
> file:close()
>
> file = io.popen("echo あいうえお| nkf -wLu | nkf --guess")
> reply = file:read("l")
> reply
UTF-8 (LF)
> file:close()
関数でラップする
cp932 = {} -- 名前空間
function cp932.utf8(s)
-- 決して echo に空文字を与えてはいけない
if s == "" then return s end
-- echo が末尾の空白を拾うので '|' を左に詰めて書く
local form = "echo %s| nkf.exe --ic=CP932 --oc=UTF-8 -Lu"
local file = io.popen(string.format(form, s))
local reply = file:read("a")
file:close()
return reply
end
lua.exe -i cp932.lua
で REPL 起動時に読み込める。
もしくは、dofile "cp932.lua"
と内部で読み込む。
> dofile "cp932.lua"
> cp932.utf8 "Hello Lua!"
Hello Lua!
> cp932.utf8 "あいうえお"
縺ゅ>縺・∴縺・
> s = [[ -- ヒアドキュメント
0123456789
abcdefghij
]]
> cp932.utf8(s)
0123456789 -- oops!
複数行文字列を与えると一行目しか返ってこない。
cp932.utf8
の内部では echo %s
があり、
string.format
によって %s
を文字列に置換している。
この置換後のコマンドライン文字列を解釈するのは cmd.exe
である。
こいつが改行以降を捨てているのだ。
ここで Lua の外側で新たなコマンドを作ることは考えない。
バッチファイルが面倒なので Lua を導入しているのだから。
echo
は一行の文字列なら問題ないと割り切る。
文字列を配列に変換する
Lua は CP932 を認識しないが、
文字列が単なるバイト列なので、どれが改行コードなのかは判定できる。
まずは、文字列を「バイトの配列」に変換してみる。
-- 標準ライブラリ string にメソッドを追加
function string:bytes() -- タイプ1
local bytes = {}
for i = 1, #self do
table.insert(bytes, self:byte(i))
end
return bytes
end
function string:bytes() -- タイプ2
return {self:byte(1, -1)}
end
-- 標準ライブラリ table に関数を追加
function table.each(t, f)
for k, v in pairs(t) do f(k, v) end
end
> dofile "cp932.lua"
> s = [[
あ
い
]]
> table.each(s:bytes(), print)
1 130 -- あ
2 160 -- あ
3 10 -- LF
4 130 -- い
5 162 -- い
6 10 -- LF
> s = "あ\r\nい\r\n"
> table.each(s:bytes(), print)
1 130 -- 一行目
2 160 -- 一行目
3 13 -- CR
4 10 -- LF
5 130 -- 二行目
6 162 -- 二行目
7 13 -- CR
8 10 -- LF
さぁ、どうする?とは考えない。
欲しいものは何か?と考える。
欲しいものは「行ごとの文字列の配列」である。
関数型パラダイム
「行ごとの文字列」とは何か?
そもそも「行」とは何か?
行:改行コード/ヌル文字で終わる文字列
改行コード:LF(0x0A), CR(0x0D), CRLF(0x0D0A)
1バイトずつ見ていって、改行コード/ヌル文字があったならば、
「それまで見てきたバイトを結合したもの」が欲しいのである。
もっと抽象的に言うと、畳み込まれた文字列が欲しいのである。
function string:reduce(acc, f)
for i = 1, #self do
acc = f(acc, self:byte(i))
end
return acc
end
string.bytes
のタイプ1にそっくりである。
これにて別タイプの string.bytes
を書ける。
function string:bytes() -- タイプ3
return self:reduce({}, function(bytes, byte)
table.insert(bytes, byte)
return bytes
end)
end
本題に戻って、
「欲しいものは何か?」と考えるので、述語を作るのは重要だ。
これは静的型付け言語で型(クラス)を作ることと同じだ。
local function is_newline(byte)
return byte == 0x0A or byte == 0x0D
end
local function is_crlf(prev, current)
return prev == 0x0D and current == 0x0A
end
local function is_eol(byte)
return byte == 0x00
end
function string:lines()
if self == "" then return {} end
local buf, prev = "", nil
local s = self .. string.char(0x00)
return s:reduce({}, function(lines, byte)
if is_eol(byte) then
table.insert(lines, buf)
return lines
end
-- ヌル文字以外の場合
if is_newline(byte) then
if not is_crlf(prev, byte) then
table.insert(lines, buf)
end
buf = ""
else -- 改行コード以外
buf = buf .. string.char(byte)
end
prev = byte
return lines
end)
end -- string.lines
> dofile "cp932.lua"
> table.each(string.lines "", print)
> table.each(string.lines "abc", print)
1 abc
> table.each(string.lines "abc\n", print)
1 abc
2
> mixed = "あ\r\nい\n\rう"
> mixed
あ
い
う
> table.each(mixed:lines(), print)
1 あ
2 い
3
4 う
Lua は \n\r
(LFCR) を1つの改行として扱っているようだ。
string.lines
では2つの改行として扱うことにした。
改行コードの混在は不要な混乱を招くので気をつけよう。
cp932.utf8(string)
やっとこ、相互変換の片道が完成。
cp932 = {}
local function win2unix(s)
if s == "" then return s end
local form = "echo %s| nkf.exe --ic=CP932 --oc=UTF-8 -Lu"
local file = io.popen(string.format(form, s))
local reply = file:read("l")
file:close()
return reply
end
function cp932.utf8(s)
if s == "" then return s end
local utf8s = {}
for _, line in ipairs(s:lines()) do
table.insert(utf8s, win2unix(line))
end
return table.concat(utf8s, "\n")
end
> dofile "cp932.lua"
> #(cp932.utf8 "")
0
> #(cp932.utf8 "\n")
1
> #(cp932.utf8 "\r\n")
1
> #(cp932.utf8 "\n\r")
2
> #(cp932.utf8 "あいうえお")
15
> cp932.utf8 "Hello 世界!"
Hello 荳也阜・・
> s = [[
0123456789
abcdefghij
]]
> cp932.utf8(s)
0123456789
abcdefghij
次回
残すは Lua 内部での UTF-8 -> CP932 変換。
echo
で nkf にどうやって UTF-8 を渡すか?
またも面倒そうである。