Какие задачи не решаются bat-файлами?

b7a94ad60377e72f6ad4713303998690.png

Бат-файлы ведут свою историю со времен 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-файлах с использованием ограниченного набора внешних команд может быть интересным упражнением.

© Habrahabr.ru