Help us understand the problem. What is going on with this article?

コマンドプロンプト・プログラミング 三次会

More than 1 year has passed since last update.

目次

Lua と nkf をつなぐ会。

CP932/UTF-8 相互変換

パイプによる変換

nkf に -w を与えると UTF-8 を吐き出してくれる。
-L で改行コード変換。

  • -Lw:Windows(CRLF)
  • -Lm:Mac(CR)※ 最新 Mac は LF
  • -Lu:Unix(LF)

--guess で文字コードを推測してくれる。

cmd.exe
> 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 は等価。

win2unix.cmd
@echo off
nkf.exe --ic=CP932 --oc=UTF-8 -Lu
exit /b
unix2win.cmd
@echo off
nkf.exe --ic=UTF-8 --oc=CP932 -Lw
exit /b
cmd.exe
> echo あいうえお | win2unix | unix2win
あいうえお

Lua から nkf を呼ぶ

os.execute([command])

Lua で外部コマンドを呼ぶときにこれを使うが、
返り値は多値にて boolean, 'exit'/'signal', errorCode が返る。
本当に欲しいであろう値は標準出力として表示される。

lua.exe
> os.execute()
true  -- 外部コマンド実行可能

> os.execute("echo あいうえお| nkf --guess")
Shift_JIS (CRLF)   -- 標準出力
true    exit    0  -- 返り値(成功、exit、エラーコード)

この標準出力を Lua 内部で拾いたい。

io.popen(prog[, mode])

これは別プロセスを作る関数で、返り値はファイルハンドルである。
パイプを作る関数と言った方が正しいか。

lua.exe
> 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.executeio.popen で実装されていると予想できる。

ここでふと気づく。
結局どうやって nkf の出力を拾えばいいのかと。

というわけで、今度は読み込みモードを試してみる。
読み込みモードは "r" を指定するが、デフォなので無指定でもよい。

lua.exe
> 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

これでパイプからの出力を拾うことができた。
だが、両モード混在はできないようだ。

lua.exe
> io.popen("nkf.exe --guess", "rw")
nil     nkf.exe --guess: Invalid argument       22

というわけで、
「nkf に文字列をパイプで渡すコマンドライン」を
読み込みモードで呼び出すこととなる。

lua.exe
> 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.lua
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" と内部で読み込む。

lua.exe
> 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 を認識しないが、
文字列が単なるバイト列なので、どれが改行コードなのかは判定できる。
まずは、文字列を「バイトの配列」に変換してみる。

cp932.lua
-- 標準ライブラリ 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
lua.exe
> 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バイトずつ見ていって、改行コード/ヌル文字があったならば、
「それまで見てきたバイトを結合したもの」が欲しいのである。

もっと抽象的に言うと、畳み込まれた文字列が欲しいのである。

cp932.lua
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 を書ける。

cp932.lua
function string:bytes() -- タイプ3
  return self:reduce({}, function(bytes, byte)
    table.insert(bytes, byte)
    return bytes
  end)
end

本題に戻って、
「欲しいものは何か?」と考えるので、述語を作るのは重要だ。
これは静的型付け言語で型(クラス)を作ることと同じだ。

cp932.lua
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
lua.exe
> 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.lua
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
lua.exe
> 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 を渡すか?
またも面倒そうである。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした