tail コマンドの作成

昨日、の続き。
昨日書いた、:getline サブルーチンだが、「空行とEOFを区別できない」という set /p コマンドの仕様からああいう機能になってしまった。しかし、実際 tail コマンドを書こうとするとどうも使いにくい。そこで、まず普通の1行入力機能を持ったサブルーチンに書き直した。どうしても状態を覚えておく必要があるので、固有の環境変数を2個使う。

:getline
if not defined __D (
 set __N=0
:g1
 set __D=
 set /p __D=
 if not defined __D (
  if !__N! geq 100 (
   set __N=
   set %1=
   set %2=1
   goto :eof
  )
  set /a __N=__N+1
  goto g1
 )
)
if %__N% gtr 0 (
 set %1=
 set /a __N=__N-1
) else (
 set __N=
 set %1=!__D!
 set __D=
)
set %2=0

ラベルg1の次の set __D= はこのロジックでは不要のはずだが、set /p を使う時はペアで必ず書くことにした。
機能は、

  • 第一引数に入力行が返る。ただし空行ならnot definedとなる
  • 第二引数は EOF なら 1 が返る。EOF でなければ 0 が返る
  • 初回呼び出しの時点で、環境変数 __D が未定義であること

制約は昨日のと同じ。

これを使って、tailコマンドは、

@echo off
setlocal enabledelayedexpansion
set __D=
if not defined LINE set LINE=20
if not exist "%~1" exit /b 1
call :tail < %1
goto :eof

:tail
set I=-%LINE%
set J=0
:loop
 call :getline DATA EOF
 if "%EOF%" == "1" goto end
 set /a J=J+1
 set SAVE%J%=!DATA!
 set /a I=I+1
 set SAVE%I%=
goto loop
:end
set /a I=I+1
if %I% leq 0 set I=1
for /l %%W in (%I%,1,%J%) do (
 if defined SAVE%%W (echo !SAVE%%W!) else echo.
)
goto :eof
<<この後にさっきの:getlineのコードが入る>>

と書けた。ファイルが長いとそれなりに時間がかかるが、途中でうっかりコントロールCを押さないように。(昨日の注意書きを参照)