Какие задачи не решаются bat-файлами?
Бат-файлы ведут свою историю со времен MS-DOS. Новые фичи добавлялись с сохранением обратной совместимости. Из-за этого многое в языке bat-файлов, как мы увидим далее, нелогично и неудобно.
Вместе с тем, в bat-файле можно использовать переменные, условия, циклы и подпрограммы. При помощи некоторых костылей можно передавать данные на вход команд и разбирать их вывод. Проще говоря, можно программировать.
В этой статье мы постараемся дать обзор основных элементов языка командного интерпретатора Windows, с помощью которых можно реализовать любой алгоритм.
Зачем же сегодня использовать этот язык? Главным образом, для автоматизации выполнения консольных команд. Запуск команд выполняется в bat-файле проще, чем в каком-либо другом языке: инструкция запуска команды – это сама команда без каких бы то ни было ключевых слов, знаков препинания и подключаемых библиотек.
Для сравнения, Java, .NET и Python для выполнения этой простой задачи требуют подключить библиотеку/сборку/модуль и вызвать метод.
Язык C++ сам по себе вообще не определяет для этого стандартный и независимый от платформы способ. Тут вам понадобится библиотека целевой операционной системы. В Windows это windows.h и функция CreateProcess()
, а в Linux надо вызвать fork()
, а затем exec()
.
Второе место по простоте вызова команд операционной системы занимает Perl – в нем достаточно заключить команду в обратные кавычки (``
).
Первое место с bat-файлами делят PowerShell и bash.
Зачем же разбирать командный интерпретатор, а не Power Shell, который, кроме всего прочего, дает доступ ко всему богатству возможностей .NET Framework?
Прежде всего, ради искусства. Разные люди делают интересные вещи чисто на bat-файлах именно потому, что это сложно. Т.е. можно рассматривать язык командного интерпретатора как эзотерический язык программирования.
Кроме того, как-то раз в реальном проекте мне пришлось поддерживать bat-скрипты. Так что рано их списывать со счетов как морально устаревшую технологию.
Еще одно полезное отличительное свойство командного интерпретатора – для получения справки не нужен ни Гугл, ни интернет. Нужно лишь набрать команду help
. Причем, информация русском языке, если у вас русская винда.
Переменные
Переменные в bat-файлах – это переменные окружения. Это значит, что все переменные, установленные скриптом, доступны процессам, которые он запускает. Это надо иметь ввиду при работе с паролями.
Переменные создаются и изменяются командой set
:
set hello=world
Создали переменную с именем hello и значением world.
Команда set
без параметров выводит список всех переменных.
Следующая строка выведет слово "world":
echo %hello%
Имена переменных нечувствительны к регистру. Следующая строка тоже выведет слово "world":
echo %HELLO%
Значения переменных могут содержать знак равенства:
set equation=x=y
echo %equation%
выведет x=y
Значение переменных вставляются в текст команды, и только потом происходит ее разбор. Это значит, что переменная может содержать саму команду и/или любое количество параметров.
Специальные переменные
Переменные, перечисленные ниже, используют один знак %, только в начале, в отличие от обычных переменных.
%0 – имя bat-файла, который сейчас выполняется, в том виде, как оно указано в командной строке. Для запуска bat-файла его имя может быть написано с полным путем, с относительным путем, без пути, с расширением, без расширения, маленькими или заглавными буквами. Соответственно, в переменной %0 оно может оказаться во всех этих вариантах. Но в любом случае командой %0 скрипт запускает сам себя.
%1, %2 … %9 – параметры. Командная строка разделяется на токены по пробелам. При этом каждая часть, заключенная в кавычки, считается одним токеном. Кавычки, если есть, тоже попадают в эти переменные.
%* – все параметры, т.е. всё, что идет в командной строке после имени bat-файла, со всеми кавычками и пробелами, какие есть.
%errorlevel% - код возврата последней команды
%CD% - текущая директория
%DATE% и %TIME% - текущие дата и время
%RANDOM% - случайное число
Преобразование параметров при подстановке
%~1 – первый параметр без кавычек. Как вы помните, параметр может быть указан либо в кавычках, либо без них, и кавычки, если они есть, будут и в переменной. От того, есть ли кавычки в переменной, зависит, будет ли содержащее ее выражение синтаксически правильным. Проверить наличие кавычек с помощью оператора if не получится, потому что в одном из двух случаев выполнение скрипта прервется из-за синтаксической ошибки. Однако, тильда перед именем переменной гарантирует, что кавычек на месте этой переменной не будет.
%~dp0 – полный путь к папке текущего bat-файла, без имени самого файла.
%~nx0 – имя и расширение текущего bat-файла, без пути
Поставив вместо нуля другую цифру, можно аналогичным образом разобрать путь, переданный скрипту через командную строку. Это также работает с переменными циклов, но не с переменными окружения.
Цикл FOR
Это единственный вид цикла. Если нужно что-то типа while или until, то придется использовать операторы if и goto.
Зато цикл FOR имеет несколько опций, выходящих за рамки того, что обычно делает цикл
Итерирование по словам
Пример
@echo off
FOR %%a in (one two three) do echo %%a
Выводит каждое слово в новой строке:
one
two
three
@echo off
отключает вывод команд. @ перед командой отключает вывод данной команды
Этот пример необходимо записать в bat-файл и запустить файл. Если вводить цикл в командной строке, то он должен выглядеть слегка иначе:
FOR %a in (one two three) do @echo %a
Да, синтаксис переменной цикла зависит от того, вводим ли мы этот цикл в командной строке или он записан в файл. Если какой-то софт запускает команду, которую вы ему укажете, и вы хотите использовать в ней цикл, синтаксис зависит от того, каким образом этот софт передает команду командному интерпретатору. Это можно делать через командную строку или через стандартный ввод. В этих случаях используется один знак процента. Вариант с файлом мы уже пробовали в примере. Там используются два знака процента.
Цикл разбирает строку в скобках по тем же правилам, что параметры командной строки: строка в кавычках – это один параметр. Пример:
@echo off
FOR %%a in ("twenty one" "twenty two") do echo %%~a
Выводит две строки:
twenty one
twenty two
Строки выводятся без кавычек, потому что использована переменная %%~a. %%a содержит кавычки.
Цикл по файлам или папкам
Следующая команда выводит список файлов в текущем каталоге:
FOR %%a in (*.*) do echo %%a
А так можно получить список папок:
FOR /D %%a in (*.*) do echo %%a
Параметр /R включает обход всех подкаталогов. При этом в переменной цикла полные пути:
FOR /R %%a in (*.*) do echo %%a
FOR /D /R %%a in (*.*) do echo %%a
Вместо "*.*
" можно писать просто "*
". Результат абсолютно такой же: в обоих случаях выбираются файлы как с расширениями, так и без.
Цикл по числам с заданным шагом
Используем параметр /L, а в скобках указываем начало, шаг и конец (включительно) через запятую. Обратите внимание, что для числа, указанного как конец интервала, команда тоже будет вызвана, если длина интервала делится на шаг. Шаг может быть отрицательным.
FOR /L %%a in (10,-1,5) do @echo %%a
Выводит:
10
9
8
7
6
5
Разбор файлов и строк
FOR /F %%a in (список.txt список2.txt) do echo %%a
Каждый файл открывается, каждая строка читается и разбивается на одну или более подстрок. Подстроки записываются в отдельные переменные. Например, если переменная цикла - %%a, то будут созданы переменные %%a, %%b, %%c и т.д.
При разборе строки разделителями по умолчанию считаются пробелы и табуляции.
Вот так можно разобрать строку, указанную непосредственно:
FOR /F %%a in (“строка”) do echo %%a
А так разбирается вывод команды. Это вообще единственный способ получить вывод команды в переменную. То есть, даже если вывод состоит из одной строки, и ее не надо разбирать, всё равно приходится писать цикл.
FOR /F %%a (‘dir /B’) do echo %%a
Выводит список файлов, разбирая вывод команды dir
.
Наберите help for
, чтобы узнать все опции.
Вызов подпрограмм и других скриптов
Очередная контринтуитивная фича bat-файлов в том, что, если из одного bat-файла вызвать другой, выполнение первого файла не продолжится, если только не использовать ключевое слово call. То есть, мы должны разбираться, чем является команда, которую мы хотим вызвать. Если это bat-файл, то надо перед его именем написать call. Если это exe-файл или встроенная команда, то это слово не нужно. Кроме того, если вызываемый скрипт использует команду exit
, то выполнение также не вернется к нам. Чтобы завершить только текущий скрипт, надо писать exit /B
.
При вызове одного скрипта из другого не создается новый процесс. Поэтому переменные окружения, установленные вызванным скриптом, видны вызывающему.
В следующем примере a.bat
вызывает b.bat
. Этот пример также демонстрирует работу с кодом завершения процесса.
a.bat
@echo off
echo Before call
call b.bat
echo b.bat returned %errorlevel%
b.bat
echo Inside b.bat
exit /B 1
Выводит:
Before call
Inside b.bat
b.bat returned 1
Командой call
также можно вызвать код, находящийся в том же файле:
@echo off
echo Before call
call :b
echo b returned %errorlevel%
goto :eof
:b
echo Inside b.bat
exit /B 1
Работает аналогично предыдущему примеру, но это один файл.
С двоеточия начинаются метки. На них можно переходить командами call
и goto
. В языке bat-файлов нет понятия подпрограммы. Мы будем так называть код, который вызывается через call
и возвращает управление через exit /B
или goto :eof
. Метка :eof не определена, и это не ошибка. Эта метка не нуждается в определении. Она всегда означает конец файла. Т.е. переход на :eof – это выход – то же, что и exit /B
, но без кода завершения.
Условия
if a==a echo 1
Выводит 1.
if a==b echo 1
Ничего не выводит.
Если строки до и после знаков равенства совпадают с учетом регистра, то команда выполняется, иначе не выполняется. В обеих строках можно использовать переменные. При этом надо иметь ввиду, что в переменных может быть всё, что угодно, включая знаки равенства и пустые строки. От значений переменных будет зависеть синтаксическая правильность команды.
Вот такой способ проверить параметр командной строки работает и при отсутствии параметра, и если в нем спецсимволы:
if "%~1"=="help" echo This is a test script && exit /B
Оператор && - это один из способов выполнить несколько команд под условием. На самом деле это ленивое «и», т.е. если первая команда вернет ненулевой errorlevel, то вторая уже не выполнится.
Другой способ – скобки:
if "%~1"=="help" (
echo This is help
exit /B
) else (
Echo This is the main part
)
Как видите, со скобками можно использовать слово else.
Насчет скобок нужно помнить одну вещь. Условие или цикл с блоком команд в скобках – это одна команда. Значения переменных в ней подставляются один раз в самом начале.
То есть, нельзя внутри такого блока присвоить значение переменной и позже в том же блоке его использовать. Нельзя присвоить значение на одной итерации цикла и использовать на другой.
При таких сценариях приходится выносить код в подпрограммы.
Есть еще поздняя подстановка переменных. Ее надо включать командой SETLOCAL ENABLEDELAYEDEXPANSION
, а вместо символа "%
" использовать "!
".
Разыменование переменных
Отсутствие в языке массивов и словарей приходится обходить, используя переменные, имена которых содержат индексы и ключи.
Допустим, переменная A содержит ключ:
set A=aaa
Записать значение по ключу можно так:
set %A%=bbb
Следующая команда выведет «bbb»:
echo %aaa%
А вот читать по ключу не так просто. Подстановка переменных не рекурсивна. То есть, нельзя, используя только знак «%
», подставить «aaa» вместо «A», а затем, «bbb» вместо «aaa».
Тут нам поможет поздняя подстановка. Она тоже не рекурсивна. Однако, используя сразу оба вида подстановок, можно выполнить замену в два этапа:
SETLOCAL ENABLEDELAYEDEXPANSION
echo !%A%!
Выводит «bbb».
Желающие усложнить себе задачу отказом от поздней подстановки, могут разбирать вывод команды set при помощи цикла for:
for /F "tokens=1,2 delims==" %%a in ('set %A%') do if "%%a"=="%A%" echo %%b
Обратите внимание, что в цикле надо проверять имя переменной, которую нам вернули, даже если указать имя нужной переменной в команде set. Дело в том, что могут существовать переменные с именами, начинающимися с нужного. Если убрать параметр команды set, результат не изменится. Но указывая этот параметр, мы оптимизируем алгоритм. Но это не точно.
Арифметика
Команда set /A
вычисляет выражения
SET /A A=2+3
ECHO %A%
Выводит 5
Замена подстрок
SET A=abcdefgh
ECHO %A:c=C%
Заменяет строчную букву C на заглавную. Выводит:
abCdefgh
Выбор подстроки:
ECHO %A:~4,2%
Выводит 2 символа, начиная с 5-го, в данном случае "ef
".
Ввод пользователя
SET /P A=Enter something:
Выведет приглашение для ввода и будет ждать ввода строки. Введенная строка окажется в переменной A.
Заключение
Язык командного интерпретатора имеет многолетнюю историю и дожил до наших дней. Он доступен на чистой винде и удобен для автоматизации выполнения команд. При этом он является языком программирования, и позволяет реализовать алгоритмы любой сложности. Самое главное в таком деле – найти подходящие инструменты командной строки, дающие доступ к нужным возможностям. Вместе с тем решение задач на bat-файлах с использованием ограниченного набора внешних команд может быть интересным упражнением.