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

# 目次

- [コマンドプロンプト・プログラミング 序](https://qiita.com/feelinguy/items/ce75348470b583b3ae42)
- [コマンドプロンプト・プログラミング 々](https://qiita.com/feelinguy/items/0b88db4aeb41bee44f19)
- [コマンドプロンプト・プログラミング 宴](https://qiita.com/feelinguy/items/ed91ed23e7baa63471a5)
- [コマンドプロンプト・プログラミング 二次会](https://qiita.com/feelinguy/items/02c70d56b2ed039db0ee) <- ★
- [コマンドプロンプト・プログラミング 三次会](https://qiita.com/feelinguy/items/f519549b0f9e8d2a714b)
- [コマンドプロンプト・プログラミング 壮行会](https://qiita.com/feelinguy/items/76dcc87020edbeb7dc14)

この二次会は予想に反して nkf に辿り着かなかった。

# パイプの挙動

コマンドプロンプトと nix シェルはパイプの挙動が違う。

`command | command` とあったとき、
コマンドプロンプトは前段コマンドの処理が終わってから後段に移る。
nix シェルの場合は、前後のコマンドが並列に動く。

nix には `yes` コマンドがある。
これは引数無しで呼ぶと無限に `y` を出力し続ける。

```shell-session:sh
$ yes | head -n 3
y
y
y
```

`yes` は OS からシグナルを受け取り終了する。
これぞ並列の力なり。

コマンドプロンプトではこうはいかない。

# ユーティリティ

## lua-rap：Lua ラッパー・テンプレート

同ディレクトリ内の自己同名 `*.lua` を実行する。

```sh:lua-rap.cmd
@echo off
lua.exe "%~dpn0.lua" %*
if %errorLevel% neq 0 ( exit /b 1 )
exit /b
```

パイプからの行数分呼ばれる可能性があるので、
エラー値をちゃんと確認しておこう。

## take：パイプからの出力行を得る

```lua:take.lua
local limit = tonumber(arg[1]) or math.huge
if limit < 0 then error("param1 out of range") end

local i = 1
for line in io.lines() do
  if i > limit then os.exit() end
  print(line)
  i = i + 1
end
```
```sh:yes.cmd
@echo off
:loop
  echo y
  goto :loop
```

[前回](https://qiita.com/feelinguy/items/ed91ed23e7baa63471a5)までの環境が整っているとする。
しかし、下記は実行しない方がいい。

```cmd.exe
> type bin\lua-rap.cmd > bin\take.cmd

> yes | take 3
y
y
y
プロセスが、存在しないパイプに書き込もうとしました。
プロセスが、存在しないパイプに書き込もうとしました。
プロセスが、存在しないパイプに書き込もうとしました。
　・
　・
　・
```

（実行してしまったならば `Ctrl+C` を押しっぱで抜けるまで待つ）
Win はこの様にシグナルを `yes` に飛ばさない。
これは PowerShell も同様である。

したがって、パイプ前段に無限ループするものを持ってきてはならない。
また、膨大な行を出力するコマンドが来るのも非効率である。

```cmd.exe
> words 1 2 3 4 5 | take
1
2
3
4
5

> words 1 2 3 4 5 | take 3
1
2
3
```

## drop：パイプからの出力行を捨てる

```lua:drop.lua
local limit = tonumber(arg[1]) or os.exit()
if limit < 0 then error("param1 out of range") end

local i = 1
for line in io.lines() do
  if i > limit then print(line) end
  i = i + 1
end
```
```cmd.exe
> type bin\lua-rap.cmd > bin\drop.cmd

> words 1 2 3 4 5 | drop

> words 1 2 3 4 5 | drop 3
4
5
```

## lua-rap の改良

`lua-rap.cmd` を単に `lua-rap.txt` と拡張子を変え、
ラッパーの雛形としてとっておく。
そして新たに、その雛形を利用する `lua-rap` を書く。

```sh:lua-rap.cmd
@echo off
if "%~1" == "" (
  echo error: param1 does not exist.
  exit /b 1
)
setLocal
  set pwd=%~dp0
  set pwd=%pwd:~0,-1%
  set template=%pwd%\%~n0.txt
  if not exist "%template%" (
    echo error: %~n0.txt does not exist.
    exit /b 2
  )
  type "%template%" > "%pwd%\%~n1.cmd"
endLocal
exit /b
```

`lua-rap name` とすると、
`lua-rap.txt` の内容を持った `name.cmd` ファイルが出来上がる。

```cmd.exe
> lua-rap drop

> words a b c d e f | drop 3
d
e
f
```

これで `type` を使わなくて済む。

## 出力行の結合

これは簡単であるが、演算子の優先順位には注意。

```cmd.exe
> puts 1 & words 2 3
1
2
3

> words a b c & words 1 2 3 | take 1
a
b
c
1

> (words 1 & words 2 3) | take 1
1
```

`|` の方が `&` よりも優先順位が高い。

## map：出力行に対するマッピング

整数をインクリメントする `incr` があるとすると、

```cmd.exe
> incr 99
100

> words 0 1 2 | map incr
1
2
3
```

こう動く。

```lua:map.lua
if #arg == 0 then error("param1 does not exist") end

for line in io.lines() do
  local cmd = string.format("%s %s", arg[1], line)
  
  if not os.execute(cmd) then
    error(string.format("%s execution error", arg[1]))
  end
end
```

`command | map incr` とすると、パイプからの行数分
`os.execute("incr <line>")` にて `incr` が起動される。

## incr：整数インクリメント

```lua:incr.lua
if #arg == 0 then error("param1 does not exist") end

local n = tonumber(arg[1])

if math.type(n) ~= "integer" then
  error("param1 must be an integer")
end

print(n + 1)
```
```cmd.exe
> lua-rap map & lua-rap incr

> words 0 1 2 | map incr
1
2
3

> words 0 1 2 | map incr | map incr
2
3
4

> words 0 1 a 3 4 | map incr
1
2
lua.exe: C:\usb\bin\incr.lua:6: param1 must be an integer
stack traceback:
        [C]: in function 'error'
        C:\usb\bin\incr.lua:6: in main chunk
        [C]: in ?
lua.exe: C:\usb\bin\map.lua:7: incr execution error
stack traceback:
        [C]: in function 'error'
        C:\usb\bin\map.lua:7: in main chunk
        [C]: in ?
```

## int：整数フィルタ

```lua:int.lua
if #arg == 0 then error("param1 does not exist") end

local n = tonumber(arg[1])

if math.type(n) == "integer" then print(n) end
```
```cmd.exe
> lua-rap int

> int a & int 1.1 & int 123 & int "789"
123
789

> words 1 a 2 b 3 c | map int
1
2
3
```

# 文字列操作への動機

例えば、ラッパー・テンプレート `lua-rap.txt` を更新したとする。
それに伴って `*.lua` のラッパーも更新する必要がある。

```cmd.exe
> dir /b bin\*.lua
drop.lua
incr.lua
int.lua
map.lua
puts.lua
take.lua
words.lua

> lua-rap words

> words drop incr int map puts take | map lua-rap

```

`words` で手打ちするのが面倒だ。

が、今回はこの辺で手を打とう。

# 閉会

この後どんなコマンドが欲しくなるだろう？
「文字列を受け取って拡張子を除く」コマンドを作り `map` に渡す？
シェルという視点からすれば悪くない気もする。

だが私は、もっと一般的なものが欲しい。

- 文字列を一文字ずつ分割
- 出力行を反転

一文字ずつ分割するには CP932 を処理しないとならない。
それを受け取るであろう Lua には UTF-8 で渡さないとならない。
また、反転処理には最低でも配列が欲しい。
環境変数を配列に見立てて操作はしたくない。

と考えていると、いつも聴こえてくる。

「糠に釘」

何が糠なのか？コマンドプロンプト？

違う！Windows だ！

「○○言語最高！」
「そう、Windows 以外ではね」

MSYS2 が頭をよぎるが、大きくてヤダ。
BusyBox が頭をよぎるが、やっぱドットファイルがヤダ。

Lua で コマンドプロンプト自体をラップするかぁ。。。

嗚呼、ほんと、二次会って嫌い。
無駄に長くなる。

Mac 喰いたくなってきたから帰りまーす。

- [コマンドプロンプト・プログラミング 三次会](https://qiita.com/feelinguy/items/f519549b0f9e8d2a714b)