cmd: интерпретатор Windows BAT/CMD для Unix — на Go, забавы ради

Маленький shell, который запускает .bat-файлы на Linux и macOS. Не очень полезный и совсем не серьёзный проект — сделал просто для удовольствия.

Что это

cmd — это интерпретатор командных файлов Windows (.bat / .cmd), написанный на Go и работающий на Unix и Linux. Он умеет запускать .bat-скрипты нативно или работать как интерактивная оболочка с синтаксисом BAT:

1
2
3
4
5
6
7
@ECHO OFF
SET NAME=World
ECHO Hello %NAME%!

FOR /L %%I IN (1,1,3) DO ECHO line %%I

IF EXIST /etc/hosts ECHO hosts на месте
1
2
3
4
5
6
$ cmd hello.bat
Hello World!
line 1
line 2
line 3
hosts на месте

Сразу честно: это не замена cmd.exe и вообще штука сомнительной полезности. Никто в здравом уме не пишет на batch под Linux — для этого есть bash, и он лучше во всём. Я сделал это, потому что мне нравится возиться с языками и интерпретаторами, а синтаксис BAT — это маленький музей странных решений, который интересно воспроизводить.

Как это устроено

Внутри — обычный конвейер интерпретатора, по стадиям на строку:

  1. Раскрытие переменных%VAR% подставляются до разбора строки, ровно как в настоящем cmd.exe.
  2. Лексер — режет строку на токены (слова, операторы |, &&, >, переменные).
  3. Парсер — рекурсивный спуск собирает токены в AST (IF, FOR, CALL, пайпы, блоки ( ... )).
  4. Исполнитель — выполняет AST: счётчик команд для GOTO, вызовы подпрограмм, циклы, перенаправления, пайпы.

Самое забавное в BAT — двухфазное раскрытие переменных. %VAR% подставляется на этапе разбора, а !VAR! (отложенное раскрытие) — уже в момент выполнения. Из-за этого %RESULT% и !RESULT! внутри одного цикла дают разные значения. Пришлось воспроизводить и это, и кучу других причуд: вывод с \r\n, поведение FOR /F "tokens=...", динамические %DATE%, %TIME%, %RANDOM%, обработку ^ как символа продолжения строки и даже несколько откровенных багов cmd.exe, на которые молча опираются реальные скрипты.

Чем проверял

Самая приятная часть. Чтобы понять, насколько интерпретатор близок к настоящему cmd.exe, мне нужен был сложный, реальный набор .bat-файлов. И он нашёлся: gw-batsic — интерпретатор GW-BASIC, написанный на самом batch. Это тысячи строк лютого BAT с эмуляцией чисел с плавающей точкой, таблицами LR-парсера, загружаемыми через имена переменных, и стек-машиной.

Если такое запускается одинаково на Windows и на моём порту — значит, совместимость серьёзная. Сейчас весь тестовый набор gw-batsic (больше тысячи проверок) проходит на cmd без единого расхождения. По дороге это вскрыло десятки тонких несовместимостей, которые я и чинил одну за другой.

Технологии и библиотеки

Тут всё скромно. Это чистый Go без экзотики:

  • Сам язык — почти всё на стандартной библиотеке: os/exec для внешних команд, path/filepath для путей и шаблонов, regexp для FINDSTR, time для %DATE%/%TIME%, math/rand для %RANDOM%.
  • Единственная внешняя зависимостьchzyer/readline для интерактивного режима: редактирование строки, история, автодополнение по Tab.

Никакого генератора парсеров — лексер и парсер написаны руками, так проще ловить причуды синтаксиса. Подробное описание реализации я сложил в docs/, если кому интересно покопаться.

Итог

Бесполезно? Скорее да. Но было увлекательно — особенно момент, когда GW-BASIC-интерпретатор, написанный на batch, впервые целиком завёлся на Linux и напечатал то же, что на Windows. Ради таких моментов всё и затевалось.