Как интерпретатор команд Windows (CMD.EXE) анализирует сценарии?

Я столкнулся с ss64.com, который предоставляет хорошую помощь относительно того, как написать пакетные сценарии, которые будет запускать интерпретатор команд Windows.

Однако мне не удалось найти хорошего объяснения грамматики пакетных сценариев, как вещи расширяются или не расширяются, и как избежать вещей.

Вот примеры вопросов, которые мне не удалось решить:

  • Как управляется система котировок? Я сделал скрипт TinyPerl
    (foreach $i (@ARGV) { print '*' . $i ; }), скомпилировал и назвал так:
    • my_script.exe "a ""b"" c" → вывод *a "b*c
    • my_script.exe """a b c""" → вывести его *"a*b*c"
  • Как работает внутренний echo командная работа? Что раскрывается внутри этой команды?
  • Почему я должен использовать for [...] %%I в файловых скриптах, но for [...] %I в интерактивных сессиях?
  • Каковы экранирующие символы и в каком контексте? Как избежать знака процента? Например, как я могу эхо %PROCESSOR_ARCHITECTURE% в прямом смысле? я нашел это echo.exe %""PROCESSOR_ARCHITECTURE% работает, есть ли лучшее решение?
  • Как сделать пары % матч? Пример:
    • set b=a, echo %a %b% c%%a a c%
    • set a =b, echo %a %b% c%bb c%
  • Как я могу гарантировать, что переменная передается команде как один аргумент, если когда-либо эта переменная содержит двойные кавычки?
  • Как хранятся переменные при использовании set команда? Например, если я сделаю set a=a" b а потом echo.%a% Я получаю a" b, Если я однако использую echo.exe из UnxUtils я получаю a b, Как идет %a% расширяется по-другому?

Спасибо за ваши огни.

8 ответов

Решение

Я провел много экспериментов, чтобы исследовать грамматику пакетных сценариев. Я также исследовал различия между пакетным режимом и режимом командной строки.

Анализатор Batch Line:

Обработка строки кода в командном файле включает несколько этапов.

Вот краткий обзор различных этапов:

Фаза 0) Читать строку:

Этап 1) Процент расширения:

Этап 1.5) Удалить <CR> : Удалить все символы возврата каретки (0x0D)

Этап 2) Обработка специальных символов, токенизация и создание кэшированного блока команд. Это сложный процесс, на который влияют такие вещи, как кавычки, специальные символы, разделители токенов и экранирование кареток.

Этап 3) Эхо анализируемых команд только если командный блок не начинался с @ и ECHO был включен в начале предыдущего шага.

Этап 4) ДЛЯ %X расширение переменной: только если активна команда FOR и команды после DO обрабатываются.

Этап 5) Отложенное расширение: только если включено отложенное расширение

Этап 5.3) Обработка канала : только если команды находятся на любой стороне канала

Этап 5.5) Выполнить перенаправление:

Этап 6) Обработка ВЫЗОВА / Удвоение каретки: Только если токен команды - ВЫЗОВ

Этап 7) Выполнить: команда выполнена


И вот подробности для каждого этапа:

Обратите внимание, что этапы, описанные ниже, являются лишь моделью того, как работает пакетный анализатор. Фактические внутренние компоненты cmd.exe могут не отражать эти фазы. Но эта модель эффективна для прогнозирования поведения пакетных скриптов.

Фаза 0) Чтение строки: чтение строки ввода.

  • При чтении строки, которая будет проанализирована как команда, <Ctrl-Z> (0x1A) читается как <LF> (LineFeed 0x0A)
  • Когда GOTO или CALL читает строки во время сканирования на: метку, <Ctrl-Z>, рассматривается как сам по себе - он не преобразуется в <LF>

Этап 1) Процент расширения:

  • Двойной %% заменяется одним %
  • Расширение аргументных переменных (%1, %2, так далее.)
  • Расширение %var%, если var не существует, замените его ничем
  • Для полного объяснения прочитайте первую половину этого из dbenham Та же тема: Percent Phase

Этап 1.5) Удалить <CR> : Удалить все возврат каретки (0x0D) из строки

Этап 2) Обработка специальных символов, токенизация и создание кэшированного блока команд. Это сложный процесс, на который влияют такие вещи, как кавычки, специальные символы, разделители токенов и экранирование кареток. Далее следует приближение этого процесса.

На этом этапе важны некоторые концепции.

  • Токен - это просто строка символов, которая рассматривается как единое целое.
  • Токены разделены разделителями токенов. Стандартные разделители токенов <space><tab>;,=<0x0B><0x0C> а также <0xFF>
    Последовательные разделители токенов рассматриваются как одно - между разделителями токенов нет пустых токенов
  • В строке в кавычках нет разделителей токенов. Вся строка в кавычках всегда обрабатывается как часть одного токена. Один токен может состоять из комбинации строк в кавычках и символов без кавычек.

Следующие символы могут иметь особое значение на этом этапе, в зависимости от контекста: ^(@&|<><LF><space><tab>;,=<0x0B><0x0C><0xFF>

Посмотрите на каждого персонажа слева направо:

  • Если это карета (^), следующий символ экранируется, а экранирующая каретка удаляется. Побег персонажи теряют все особое значение (за исключением <LF>).
  • Если это цитата ("), переключите флаг кавычки. Если флаг цитаты активен, то только " а также <LF> особенные. Все остальные символы теряют свое особое значение, пока следующая цитата не отключит флаг кавычки. Невозможно избежать закрывающей цитаты. Все цитируемые символы всегда находятся в одном и том же токене.
  • <LF> всегда отключает флаг кавычки. Другое поведение варьируется в зависимости от контекста, но кавычки никогда не изменяют поведение <LF>,
    • Сбежал <LF>
      • <LF> раздели
      • Следующий персонаж сбежал. Если в конце строки находится буфер, то следующая строка читается и добавляется к текущей перед экранированием следующего символа. Если следующий символ <LF>, то это трактуется как литерал, то есть этот процесс не является рекурсивным.
    • Unescaped <LF> не в скобках
      • <LF> удаляется и анализ текущей строки прекращается.
      • Любые оставшиеся символы в буфере строк просто игнорируются.
    • Unescaped <LF> внутри блока IN IN в скобках
      • <LF> превращается в <space>
      • Если в конце буфера строки, то следующая строка читается и добавляется к текущей.
    • Unescaped <LF> внутри заключенного в скобки командного блока
      • <LF> превращается в <LF><space> и <space> рассматривается как часть следующей строки командного блока.
      • Если в конце строки находится буфер, то следующая строка читается и добавляется в пробел.
  • Если это один из специальных символов &|< или же > разделите строку в этой точке, чтобы обработать каналы, объединение команд и перенаправление.
    • В случае трубы (|), каждая сторона представляет собой отдельную команду (или блок команд), которая получает специальную обработку на этапе 5.3
    • В случае &, &&, или же || объединение команд, каждая сторона объединения рассматривается как отдельная команда.
    • В случае <, <<, >, или же >> При перенаправлении предложение перенаправления анализируется, временно удаляется, а затем добавляется в конец текущей команды. Предложение перенаправления состоит из необязательной цифры дескриптора файла, оператора перенаправления и маркера назначения перенаправления.
      • Если токен, который предшествует оператору перенаправления, представляет собой одну цифру, то эта цифра указывает дескриптор файла для перенаправления. Если маркер дескриптора не найден, по умолчанию перенаправление вывода равно 1 (стандартный вывод), а перенаправление ввода по умолчанию равно 0 (стандартный ввод).
  • Если самый первый токен для этой команды (до перемещения перенаправления в конец) начинается с @ тогда @ имеет особое значение. (@ не является особенным в любом другом контексте)
    • Специальный @ устранен.
    • Если ECHO включен, то эта команда вместе со всеми последующими каскадными командами в этой строке исключаются из эхо-сигнала фазы 3. Если @ перед открытием (, тогда весь заключенный в скобки блок исключен из эхо-сигнала фазы 3.
  • Круглая скобка процесса (содержит составные операторы в несколько строк):
    • Если парсер не ищет токен команды, то ( не особенный.
    • Если парсер ищет токен команды и находит (, затем запустите новый составной оператор и увеличьте счетчик скобок
    • Если счетчик скобок равен> 0, то ) завершает составной оператор и уменьшает счетчик скобок.
    • Если достигнут конец строки и счетчик скобок равен> 0, то следующая строка будет добавлена ​​к составному оператору (начинается снова с фазы 0)
    • Если счетчик скобок равен 0 и анализатор ищет команду, тогда ) функции, аналогичные REM оператор, если сразу за ним следует разделитель токена, специальный символ, символ новой строки или конец файла
      • Все специальные символы теряют свое значение, кроме ^ (возможна конкатенация строк)
      • Как только достигнут конец логической строки, вся "команда" отбрасывается.
  • Каждая команда разбирается на серию токенов. Первый токен всегда обрабатывается как командный токен (после специального @ были удалены и переадресация перенесена в конец).
    • Ведущие разделители токенов перед командным токеном удаляются
    • При разборе токена команды, ( функционирует как командный разделитель токенов, в дополнение к стандартным разделителям токенов
    • Обработка последующих токенов зависит от команды.
  • Большинство команд просто объединяют все аргументы после токена команды в один токен аргумента. Все разделители токенов аргументов сохраняются. Параметры аргумента обычно не анализируются до фазы 7.
  • Три команды получают специальную обработку - IF, FOR и REM
    • Если IF разделен на две или три отдельные части, которые обрабатываются независимо. Синтаксическая ошибка в конструкции IF приведет к фатальной синтаксической ошибке.
      • Операция сравнения - это действительная команда, которая проходит всю фазу до фазы 7
        • Все опции IF полностью проанализированы на этапе 2.
        • Последовательные разделители токенов разрушаются в одно пространство.
        • В зависимости от оператора сравнения будут определены один или два токена значения.
      • Истинный командный блок представляет собой набор команд после условия и анализируется, как и любой другой командный блок. Если нужно использовать ELSE, тогда блок True должен быть заключен в скобки.
      • Необязательный блок False command - это набор команд после ELSE. Опять же, этот командный блок анализируется нормально.
      • Командные блоки True и False не переходят автоматически в последующие фазы. Их последующая обработка контролируется фазой 7.
    • FOR делится на две после DO. Синтаксическая ошибка в конструкции FOR приведет к фатальной синтаксической ошибке.
      • Часть через DO является фактической итерационной командой FOR, которая проходит всю фазу 7
        • Все опции FOR полностью анализируются на этапе 2.
        • Заключенное в скобки предложение IN рассматривает <LF> как <space>, После разбора предложения IN все токены объединяются в один токен.
        • Последовательные разделители токенов без кавычек / без кавычек сворачиваются в единое пространство по всей команде FOR через DO.
      • Часть после DO является командным блоком, который анализируется нормально. Последующая обработка командного блока DO контролируется итерацией в фазе 7.
    • REM, обнаруженный в фазе 2, обрабатывается совершенно иначе, чем все другие команды.
      • Анализируется только один токен аргумента - анализатор игнорирует символы после первого токена аргумента.
      • Если есть только один токен аргумента, который заканчивается неэкранированным ^ это завершает строку, затем токен аргумента отбрасывается, а последующая строка анализируется и добавляется в REM. Это повторяется до тех пор, пока не останется более одного токена или последний символ не будет ^,
      • Команда REM может появиться в выходных данных фазы 3, но команда никогда не выполняется, и исходный текст аргумента отображается - экранирующие символы не удаляются.
  • Если токен команды начинается с :, и это первый раунд фазы 2 (не перезапуск из-за CALL в фазе 6), затем
    • Маркер обычно рассматривается как неисполненная метка.
      • Остальная часть строки анализируется, однако ), <, >, & а также | больше не имеют особого значения. Весь остаток строки считается частью метки "команда".
      • ^ продолжает быть особенным, означая, что продолжение строки может использоваться для добавления следующей строки к метке.
      • Неисполненная метка в заключенном в скобки блоке приведет к фатальной синтаксической ошибке, если за ней сразу не последует команда или метка выполнения в следующей строке.
        • Обратите внимание, что ( больше не имеет особого значения для первой команды, которая следует за неисполненной меткой в этом контексте.
      • Команда отменяется после завершения анализа метки. Последующие этапы не имеют места для этикетки
    • Существует три исключения, которые могут привести к тому, что метка, найденная на этапе 2, будет рассматриваться как исполняемая метка, которая продолжает анализировать на этапе 7.
      • Существует перенаправление, которое предшествует метке метки, и есть | труба или &, &&, или же || объединение команд на линии.
      • Существует перенаправление, которое предшествует токену метки, и команда находится в заключенном в скобки блоке.
      • Маркерный токен - это самая первая команда в строке внутри блока, заключенного в скобки, а строка выше оканчивается неисполненной меткой.
    • Следующее происходит, когда исполняемая метка обнаружена на этапе 2
      • Метка, ее аргументы и перенаправление исключены из любого эхо-выхода в фазе 3
      • Любые последующие составные команды в строке полностью анализируются и выполняются.
    • Для получения дополнительной информации о выполненных ярлыках и неисполненных ярлыках см. Https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405.

Этап 3) Эхо анализируемых команд только если командный блок не начинался с @ и ECHO был включен в начале предыдущего шага.

Этап 4) ДЛЯ %X расширение переменной: только если активна команда FOR и команды после DO обрабатываются.

  • На этом этапе фаза 1 пакетной обработки уже преобразует переменную FOR, например %%X в %X, Командная строка имеет различные правила процентного расширения для фазы 1. Это причина, по которой командные строки используют %X но командные файлы используют %%X для переменных.
  • Имена переменных FOR чувствительны к регистру, но ~modifiers не чувствительны к регистру.
  • ~modifiers иметь приоритет над именами переменных. Если персонаж после ~ является одновременно модификатором и допустимым именем переменной FOR, и существует последующий символ, который является активным именем переменной FOR, тогда этот символ интерпретируется как модификатор.
  • Имена переменных FOR являются глобальными, но только в контексте предложения DO. Если подпрограмма вызывается из предложения FOR DO, то переменные FOR не раскрываются в подпрограмме CALLed. Но если подпрограмма имеет собственную команду FOR, то все определенные в данный момент переменные FOR доступны для внутренних команд DO.
  • Имена переменных FOR могут быть повторно использованы во вложенных FOR. Внутреннее значение FOR имеет приоритет, но после закрытия INNER FOR внешнее значение FOR восстанавливается.
  • Если ECHO был включен в начале этой фазы, то фаза 3) повторяется, чтобы показать проанализированные команды DO после расширения переменных FOR.

---- С этого момента каждая команда, указанная на этапе 2, обрабатывается отдельно.
---- Фазы с 5 по 7 завершены для одной команды, прежде чем перейти к следующей.

Этап 5) Отложенное расширение: только если включено отложенное расширение

  • Если команда находится в заключенном в скобки блоке с любой стороны канала, пропустите этот шаг.
  • Каждый токен для команды анализируется для отложенного расширения независимо.
    • Большинство команд анализируют два или более токенов - токен команды, токен аргументов и каждый маркер назначения перенаправления.
    • Команда FOR анализирует только токен предложения IN.
    • Команда IF анализирует только значения сравнения - одно или два, в зависимости от оператора сравнения.
  • Для каждого проанализированного токена сначала проверьте, содержит ли он какой-либо !, Если нет, то токен не анализируется - важно для ^ персонажи. Если токен содержит ! затем просканируйте каждый символ слева направо:
    • Если это карета (^) следующий символ не имеет особого значения, сама каретка удалена
    • Если это восклицательный знак, ищите следующий восклицательный знак (каретки больше не наблюдаются), разверните значение переменной.
      • Последовательное открытие ! свернуты в один !
      • Любой оставшийся ! что не может быть в паре удаляется
    • Важно: на этом этапе кавычки и другие специальные символы игнорируются
    • Расширение vars на этом этапе "безопасно", потому что специальные символы больше не обнаруживаются (даже <CR> или же <LF>)
    • Для более полного объяснения прочитайте вторую половину из той же ветки dbenham - Фаза восклицательного знака
    • Есть некоторые крайние случаи, когда эти правила, кажется, терпят неудачу:
      См. Отложенное расширение в некоторых случаях не удается

Этап 5.3) Обработка канала : только если команды находятся на любой стороне канала
Каждая сторона трубы обрабатывается независимо.

  • Если иметь дело с командным блоком в скобках, то все <LF> с командой до и после преобразуются в <space>&, Другой <LF> раздели
  • Команда (или блок команд) выполняется асинхронно в новом потоке cmd.exe через
    %comspec% /S /D /c" commandBlock", Это означает, что командный блок получает фазовый перезапуск, но на этот раз в режиме командной строки.
  • Это конец обработки для конвейерных команд.
  • Для получения дополнительной информации о том, как каналы анализируются и обрабатываются, посмотрите на этот вопрос и ответы: почему задержанное расширение терпит неудачу, когда внутри переданного по трубопроводу блока кода?

Этап 5.5) Выполнить перенаправление: любое перенаправление, обнаруженное на этапе 2, теперь выполняется.

Этап 6) Обработка CALL / Удвоение каретки: только если токен команды - CALL, или если текст перед первым встречающимся стандартным разделителем токена - CALL. Если CALL анализируется из большего токена команды, то перед продолжением неиспользованная часть добавляется к токену аргументов.

  • Сканирование токенов аргументов на наличие кавычек /?, Если найден где-нибудь в пределах токенов, прервите фазу 6 и перейдите к Фазе 7, где будет напечатана ПОМОЩЬ для ВЫЗОВА.
  • Удалить первый CALL Таким образом, несколько вызовов могут быть сложены
  • Удвойте все кареты
  • Перезапустите фазы 1, 1.5 и 2, но не переходите к фазе 3
    • Любые двойные каретки сокращаются до одной каретки, если они не указаны. Но, к сожалению, котировки остаются вдвое.
    • Кэшированный командный блок уже был подготовлен в исходном раунде этапа 2. Поэтому многие задачи этапа 2 изменены
      • Любое вновь появляющееся перенаправленное без кавычек, неэкранированное, которое не было обнаружено в первом раунде фазы 2, обнаружено, но оно удалено (включая имя файла) без фактического выполнения перенаправления
      • Любая вновь появляющаяся нецитированная, неэкранированная каретка в конце строки удаляется без продолжения строки
      • CALL отменяется без ошибок, если обнаруживается любое из следующего
        • Вновь появившийся без кавычек & или же |
        • Полученный токен команды начинается с кавычек, неэкранированных (
        • Самый первый токен после удаленного CALL начался с @
      • Если результирующая команда является, по-видимому, допустимым IF или FOR, то впоследствии выполнение завершится с ошибкой, сообщающей, что IF или же FOR не распознается как внутренняя или внешняя команда.
      • Конечно, CALL не прерывается во втором раунде фазы 2, если получающийся в результате токен команды является меткой, начинающейся с :,
    • Есть некоторые крайние случаи, когда эти правила не выполняются:
      См. Исследование Linefeeds с CALL
  • Если результирующим токеном команды является CALL, перезапустите этап 6 (повторяется до тех пор, пока CALL больше не будет)
  • Если результирующий токен команды представляет собой пакетный сценарий или метку:, то выполнение CALL полностью обрабатывается оставшейся частью этапа 6.
    • Переместите текущую позицию файла пакетного сценария в стек вызовов, чтобы выполнение могло возобновиться с правильной позиции после завершения вызова.
    • Установите маркеры аргументов%0, %1, %2, ...%N и%* для CALL, используя все полученные токены
    • Если маркер команды является меткой, которая начинается с :, затем
      • Перезапустите этап 5. Это может повлиять на то, что: CALLED. Но поскольку токены% 0 и т. Д. Уже настроены, они не изменят аргументы, передаваемые подпрограмме CALLed.
      • Выполните метку GOTO, чтобы поместить указатель файла в начало подпрограммы (не обращайте внимания на любые другие токены, которые могут следовать за меткой:). См. На этапе 7 правила о том, как работает GOTO.
    • В противном случае передать управление указанному пакетному сценарию.
    • Выполнение метки или сценария CALLed: продолжается до тех пор, пока не будет достигнут EXIT /B или конец файла, после чего стек CALL будет извлечен, и выполнение возобновится с позиции сохраненного файла.
      Этап 7 не выполняется для скриптов CALLed или: меток.
  • В противном случае результат фазы 6 попадает в фазу 7 для выполнения.

Этап 7) Выполнить: команда выполнена

  • 7.1 - Выполнить внутреннюю команду - если токен команды указан в кавычках, пропустите этот шаг. В противном случае попытайтесь разобрать внутреннюю команду и выполнить.
    • Следующие тесты сделаны, чтобы определить, представляет ли токен команды без кавычек внутреннюю команду:
      • Если маркер команды точно соответствует внутренней команде, то выполните ее.
      • Иначе сломайте токен команды до первого появления +/[ ]<space><tab>,; или же =
        Если предыдущий текст является внутренней командой, то запомните эту команду
        • Если в режиме командной строки, или если команда поступает из блока в скобках, командного блока ЕСЛИ истина или ложь, командного блока FOR DO или связана с объединением команд, выполните внутреннюю команду
        • В противном случае (должна быть автономной командой в пакетном режиме) сканировать текущую папку и PATH на наличие файла.COM, .EXE, .BAT или.CMD, базовое имя которого соответствует исходному токену команды.
          • Если первый соответствующий файл -.BAT или.CMD, перейдите к 7.3.exec и выполните этот сценарий.
          • В противном случае (совпадение не найдено или первое совпадение -.EXE или.COM) выполните запомненную внутреннюю команду.
      • Иначе сломайте токен команды до первого появления .\ или же :
        Если предыдущий текст не является внутренней командой, перейдите к 7.2.
        В противном случае предыдущий текст может быть внутренней командой. Запомните эту команду.
      • Прервите токен команды до первого появления +/[]<space><tab>,; или же =
        Если предыдущий текст является путем к существующему файлу, перейдите к 7.2.
        В противном случае выполните запомненную внутреннюю команду.
    • Если внутренняя команда анализируется из большего токена команды, то неиспользованная часть токена команды включается в список аргументов
    • То, что маркер команды анализируется как внутренняя команда, не означает, что она будет выполнена успешно. Каждая внутренняя команда имеет свои собственные правила относительно того, как анализируются аргументы и параметры, и какой синтаксис разрешен.
    • Все внутренние команды выводят справку вместо выполнения своих функций, если /? обнаружен. Большинство признают /? если это появляется где-нибудь в аргументах. Но некоторые команды, такие как ECHO и SET, выводят справку только в том случае, если токен первого аргумента начинается с /?,
    • У SET есть интересная семантика:
      • Если команда SET содержит кавычку перед именем переменной
        set "name=content" ignored -> значение = content
        затем в качестве содержимого используется текст между первым знаком равенства и последней цитатой (исключая первое равенство и последнюю цитату). Текст после последней цитаты игнорируется. Если после знака равенства нет кавычек, то остальная часть строки используется в качестве содержимого.
      • Если команда SET не имеет кавычки перед именем
        set name="content" not ignored -> значение = "content" not ignored
        затем весь остаток строки после равенства используется в качестве содержимого, включая любые и все кавычки, которые могут присутствовать.
    • Сравнение ПЧ оценивается, и в зависимости от того, является ли условие истинным или ложным, обрабатывается соответствующий уже проанализированный зависимый блок команд, начиная с фазы 5.
    • Предложение IN команды FOR повторяется соответствующим образом.
      • Если это FOR /F, который повторяет вывод командного блока, то:
        • Предложение IN выполняется в новом процессе cmd.exe через CMD /C.
        • Блок команд должен пройти весь процесс синтаксического анализа во второй раз, но на этот раз в контексте командной строки
        • ECHO запустится ВКЛ, а отложенное расширение обычно будет отключено (зависит от настроек реестра)
        • Все изменения среды, сделанные командным блоком предложения IN, будут потеряны после завершения дочернего процесса cmd.exe.
      • Для каждой итерации:
        • Определены значения переменных FOR
        • Уже обработанный блок команд DO обрабатывается, начиная с фазы 4.
    • GOTO использует следующую логику для поиска метки:
      • Метка анализируется с первого аргумента токена
      • Скрипт сканируется для следующего появления метки
        • Сканирование начинается с текущей позиции файла
        • Если достигнут конец файла, то сканирование возвращается к началу файла и продолжается до исходной начальной точки.
      • Сканирование останавливается при самом первом вхождении найденной метки, и указатель файла устанавливается на строку, следующую непосредственно за меткой. Выполнение сценария возобновляется с этого момента. Обратите внимание, что успешное истинное GOTO немедленно прервет любой проанализированный блок кода, включая циклы FOR.
      • Если метка не найдена или токен метки отсутствует, то GOTO завершается ошибкой, печатается сообщение об ошибке и извлекается стек вызовов. Это эффективно работает как EXIT /B, за исключением того, что все уже проанализированные команды в текущем командном блоке, которые следуют за GOTO, все еще выполняются, но в контексте CALLer (контекст, который существует после EXIT /B)
      • См. https://www.dostips.com/forum/viewtopic.php?f=3&t=3803 для более точного описания правил, используемых для анализа меток.
    • RENAME и COPY принимают подстановочные знаки для исходного и целевого путей. Но Microsoft делает ужасную работу, документируя, как работают шаблоны, особенно для целевого пути. Полезный набор правил подстановочных знаков можно найти в разделе Как команда Windows RENAME интерпретирует подстановочные знаки?
  • 7.2 - Выполнить изменение громкости - иначе, если токен команды не начинается с кавычки, имеет длину ровно два символа, а 2-й символ - двоеточие, измените громкость
    • Все токены аргументов игнорируются
    • Если том, указанный первым символом, не может быть найден, прервать с ошибкой
    • Командный токен :: всегда будет приводить к ошибке, если SUBST не используется для определения объема для ::
      Если SUBST используется для определения объема для ::, тогда громкость будет изменена, она не будет рассматриваться как метка.
  • 7.3 - Выполнить внешнюю команду - иначе попытайтесь обработать команду как внешнюю команду.
    • Если вторым символом токена команды является двоеточие, проверьте, найден ли том, указанный первым знаком.
      Если том не может быть найден, прервитесь с ошибкой.
    • Если в пакетном режиме и токен команды начинается с :, затем перейдите к 7.4
      Обратите внимание, что если метка метки начинается с ::, тогда это не будет достигнуто, потому что предыдущий шаг будет прерван с ошибкой, если SUBST не используется для определения тома для ::,
    • Определите внешнюю команду для выполнения.
      • Это сложный процесс, который может включать в себя текущий том, текущий каталог, переменную PATH, переменную PATHEXT и / или ассоциации файлов.
      • Если действительная внешняя команда не может быть идентифицирована, прервитесь с ошибкой.
    • Если в режиме командной строки и токен команды начинается с :, затем перейдите к 7.4
      Обратите внимание, что это редко достигается, потому что предыдущий шаг будет прерван с ошибкой, если токен команды не начинается с :: и SUBST используется для определения громкости :: и весь токен команды является допустимым путем к внешней команде.
    • 7.3.exec - выполнить внешнюю команду.
  • 7.4 - Игнорировать метку - Игнорировать команду и все ее аргументы, если маркер команды начинается с :,
    Правила в 7.2 и 7.3 могут помешать этикетке достичь этой точки.

Парсер командной строки:

Работает как BatchLine-Parser, за исключением:

Этап 1) Процент расширения:

  • %var% по-прежнему заменяется содержимым var, но если var не определен, выражение не изменится.
  • Нет специальной обработки %%, Если var = content, то %%var%% расширяется до %content%,

Этап 3) Отобразить проанализированные команды

  • Это не выполняется после фазы 2. Это выполняется только после фазы 4 для командного блока FOR DO.

Этап 5) Отсроченное расширение: только если включено Delayed Expansion

  • !var! по-прежнему заменяется содержимым var, но если var не определен, выражение не изменится.

Этап 7) Выполнить команду

  • Попытки CALL или GOTO a:label приводят к ошибке.
  • Даже если метки не могут быть вызваны, допустимая строка все равно может содержать метку. Как уже задокументировано на этапе 7, выполненная метка может привести к ошибке при различных сценариях.
    • Пакетно выполненные метки могут вызвать ошибку, только если они начинаются с ::
    • Выполненные в командной строке метки почти всегда приводят к ошибке

Разбор целочисленных значений

Существует много разных контекстов, в которых cmd.exe анализирует целочисленные значения из строк, и правила противоречивы:

  • SET /A
  • IF
  • %var:~n,m% (расширение подстроки переменной)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

Подробности этих правил можно найти в Правилах того, как CMD.EXE анализирует числа


Для тех, кто хочет улучшить эти правила, есть тема для обсуждения на форуме DosTips, где можно сообщать о проблемах и вносить предложения.

Надеюсь, поможет
Ян Эрик (Jeb) - оригинальный автор и первооткрыватель различных фаз
Dave Benham (dbenham) - много дополнительного контента и редактирования

При вызове команды из командного окна токенизация аргументов командной строки не выполняется cmd.exe (он же "оболочка"). Чаще всего токенизация выполняется во время выполнения C/C++ вновь сформированных процессов, но это не обязательно так, например, если новый процесс не был написан на C/C++ или если новый процесс игнорирует argv и обработать необработанную командную строку для себя (например, с помощью GetCommandLine ()). На уровне операционной системы Windows передает необработанные командные строки как единую строку новым процессам. Это в отличие от большинства *nix-оболочек, где оболочка токенизирует аргументы согласованным, предсказуемым образом перед передачей их вновь сформированному процессу. Все это означает, что вы можете столкнуться с сильно различающимся поведением токенизации аргументов в разных программах Windows, поскольку отдельные программы часто берут токенизацию аргументов в свои руки.

Если это звучит как анархия, это отчасти так. Тем не менее, так как большое количество программ Windows используют среду выполнения Microsoft C/C++ argv может быть в целом полезно понять, как MSVCRT токенизирует аргументы. Вот выдержка:

  • Аргументы ограничиваются пробелом, который является пробелом или табуляцией.
  • Строка, заключенная в двойные кавычки, интерпретируется как один аргумент, независимо от пробела, содержащегося внутри. Строка в кавычках может быть встроена в аргумент. Обратите внимание, что каретка (^) не распознается как escape-символ или разделитель.
  • Двойная кавычка, которой предшествует обратная косая черта, \", интерпретируется как буквальная двойная кавычка (").
  • Обратные слеши интерпретируются буквально, если только они не предшествуют двойной кавычке.
  • Если за четным числом обратной косой черты следует двойная кавычка, то одна обратная косая черта () помещается в массив argv для каждой пары обратной косой черты (\), а двойная кавычка (") интерпретируется как разделитель строк.
  • Если за нечетным числом обратных косых черт следует двойная кавычка, то одна обратная косая черта () помещается в массив argv для каждой пары обратных косых черт (\), а оставшаяся обратная косая черта интерпретируется как escape-последовательность, вызывая буквальная двойная кавычка ("), помещаемая в argv.

Microsoft "пакетный язык" (.bat) не является исключением из этой анархической среды, и она разработала собственные уникальные правила токенизации и экранирования. Похоже, что командная строка cmd.exe выполняет некоторую предварительную обработку аргумента командной строки (в основном для подстановки и экранирования переменных) перед передачей аргумента новому исполняющемуся процессу. Вы можете прочитать больше о низкоуровневых деталях языка пакетной обработки и о том, что cmd можно найти в отличных ответах jeb и dbenham на этой странице.


Давайте создадим простую утилиту командной строки в C и посмотрим, что она говорит о ваших тестовых примерах:

int main(int argc, char* argv[]) {
    int i;
    for (i = 0; i < argc; i++) {
        printf("argv[%d][%s]\n", i, argv[i]);
    }
    return 0;
}

(Примечания: argv[0] - это всегда имя исполняемого файла, для краткости оно опущено ниже. Протестировано в Windows XP SP3. Скомпилировано с Visual Studio 2005.)

> test.exe "a ""b"" c"
argv[1][a "b" c]

> test.exe """a b c"""
argv[1]["a b c"]

> test.exe "a"" b c
argv[1][a" b c]

И несколько моих собственных тестов:

> test.exe a "b" c
argv[1][a]
argv[2][b]
argv[3][c]

> test.exe a "b c" "d e
argv[1][a]
argv[2][b c]
argv[3][d e]

> test.exe a \"b\" c
argv[1][a]
argv[2]["b"]
argv[3][c]

Вот расширенное объяснение Фазы 1 в ответе Джеба (действительно как для пакетного режима, так и для режима командной строки).

Этап 1) Процентное расширение Начиная слева, отсканируйте каждый символ на наличие %, Если найден

  • 1.1 (побег % ) пропущен, если режим командной строки
    • Если в пакетном режиме и следуют другие % затем
      замещать %% с одним % и продолжить сканирование
  • 1.2 (развернуть аргумент) пропускается, если режим командной строки
    • Иначе если пакетный режим то
      • Если после * и расширения команды включены тогда
        замещать %* с текстом всех аргументов командной строки (Замените ничем, если нет аргументов) и продолжите сканирование.
      • Иначе, если последует <digit> затем
        замещать %<digit> со значением аргумента (замените ничем, если не определено) и продолжите сканирование.
      • Иначе, если последует ~ и расширения команды включены тогда
        • Если за ним следует необязательный допустимый список модификаторов аргумента, за которым следует обязательный <digit> затем
          замещать %~[modifiers]<digit> с измененным значением аргумента (замените ничем, если не определено или если указан $PATH: модификатор не определен) и продолжите сканирование.
          Примечание: модификаторы нечувствительны к регистру и могут появляться несколько раз в любом порядке, кроме $PATH: модификатор может появляться только один раз и должен быть последним модификатором перед <digit>
        • В противном случае неверный синтаксис измененного аргумента вызывает фатальную ошибку: все проанализированные команды прерываются, и пакетная обработка прерывается, если в пакетном режиме!
  • 1.3 (развернуть переменную)
    • Иначе, если расширения команд отключены, то
      Посмотрите на следующую строку символов, ломая перед % или же <LF> и называть их VAR (может быть пустой список)
      • Если следующий символ % затем
        • Если VAR определен, то
          замещать %VAR% со значением VAR и продолжить сканирование
        • Иначе если пакетный режим то
          Удалить %VAR% и продолжить сканирование
        • Остальное goto 1.4
      • Остальное goto 1.4
    • Иначе, если расширения команды включены тогда
      Посмотрите на следующую строку символов, ломая перед %: или же <LF> и назовите их VAR (может быть пустой список). Если VAR ломается раньше : и последующий символ % затем включить : как последний символ в VAR и перерыв перед %,
      • Если следующий символ % затем
        • Если VAR определен, то
          замещать %VAR% со значением VAR и продолжить сканирование
        • Иначе если пакетный режим то
          Удалить %VAR% и продолжить сканирование
        • Остальное goto 1.4
      • Иначе, если следующий символ : затем
        • Если VAR не определен, то
          • Если пакетный режим то
            Удалить %VAR: и продолжить сканирование.
          • Остальное goto 1.4
        • Иначе, если следующий символ ~ затем
          • Если следующая строка символов соответствует шаблону [integer][,[integer]]% затем
            замещать %VAR:~[integer][,[integer]]% с подстрокой значения VAR (возможно, приводящей к пустой строке) и продолжить сканирование.
          • Остальное goto 1.4
        • Иначе, если последует = или же *= затем
          Неверный синтаксис поиска и замены переменных приводит к фатальной ошибке: все проанализированные команды прерываются, а пакетная обработка прерывается, если в пакетном режиме!
        • Иначе, если следующая строка символов соответствует шаблону [*]search=[replace]% где поиск может включать в себя любой набор символов, кроме = а также <LF> и замена может включать любой набор символов, кроме % а также <LF> затем заменить
          %VAR:[*]search=[replace]% со значением VAR после выполнения поиска и замены (возможно, приводящего к пустой строке) и продолжения сканирования
        • Остальное goto 1.4
  • 1,4 (полоса%)
    • Иначе, если в пакетном режиме
      Удалить % и продолжить сканирование
    • Остальное сохранить % и продолжить сканирование

Вышесказанное помогает объяснить, почему эта партия

@echo off
setlocal enableDelayedExpansion
set "1var=varA"
set "~f1var=varB"
call :test "arg1"
exit /b  
::
:test "arg1"
echo %%1var%% = %1var%
echo ^^^!1var^^^! = !1var!
echo --------
echo %%~f1var%% = %~f1var%
echo ^^^!~f1var^^^! = !~f1var!
exit /b

Дает эти результаты:

%1var% = "arg1"var
!1var! = varA
--------
%~f1var% = P:\arg1var
!~f1var! = varB

Примечание 1 - Этап 1 происходит до признания операторов REM. Это очень важно, потому что это означает, что даже замечание может привести к фатальной ошибке, если у него недопустимый синтаксис расширения аргумента или неверный синтаксис поиска и замены переменных!

@echo off
rem %~x This generates a fatal argument expansion error
echo this line is never reached

Примечание 2. Еще одно интересное следствие правил% parsing: переменные, содержащие в имени: могут быть определены, но их нельзя развернуть, если не отключены расширения команд. Есть одно исключение - имя переменной, содержащее один двоеточие в конце, может быть расширено, если включены расширения команд. Однако вы не можете выполнять подстроку или операции поиска и замены над именами переменных, заканчивающимися двоеточием. Пакетный файл ниже (любезно предоставлен Jeb) демонстрирует это поведение

@echo off
setlocal
set var=content
set var:=Special
set var::=double colon
set var:~0,2=tricky
set var::~0,2=unfortunate
echo %var%
echo %var:%
echo %var::%
echo %var:~0,2%
echo %var::~0,2%
echo Now with DisableExtensions
setlocal DisableExtensions
echo %var%
echo %var:%
echo %var::%
echo %var:~0,2%
echo %var::~0,2%

Примечание 3 - Интересный результат порядка правил синтаксического анализа, который Джеб излагает в своем посте: при выполнении поиска и замены обычным расширением НЕЛЬЗЯ экранировать специальные символы (хотя они могут быть заключены в кавычки). Но при выполнении поиска и замены с отложенным расширением специальные символы ДОЛЖНЫ быть экранированы (если они не заключены в кавычки).

@echo off
setlocal enableDelayedExpansion
set "var=this & that"
echo %var:&=and%
echo "%var:&=and%"
echo !var:^&=and!
echo "!var:&=and!"

Вот расширенное и более точное объяснение фазы 5 в ответе Джеба (действительно как для пакетного режима, так и для режима командной строки)

Обратите внимание, что есть некоторые крайние случаи, когда эти правила не выполняются:
См. Проверка Linefeeds с CALL

Этап 5) Отложенное расширение Только в том случае, если включено отложенное расширение и строка содержит хотя бы одно !, затем, начиная слева, отсканируйте каждый символ на наличие ^ или же ! и если найден, то

  • 5.1 (уход каретки) Необходим для ! или же ^ литералы
    • Если персонаж является каретой ^ затем
      • Удалить ^
      • Отсканируйте следующий символ и сохраните его как литерал
      • Продолжить сканирование
  • 5.2 (развернуть переменную)
    • Если персонаж !, затем
      • Если расширения команд отключены, то
        Посмотрите на следующую строку символов, ломая перед ! или же <LF> и называть их VAR (может быть пустой список)
        • Если следующий символ ! затем
          • Если VAR определен, то
            замещать !VAR! со значением VAR и продолжить сканирование
          • Иначе если пакетный режим то
            Удалить !VAR! и продолжить сканирование
          • Остальное goto 5.2.1
        • Остальное goto 5.2.1
      • Иначе, если расширения команды включены тогда
        Посмотрите на следующую строку символов, ломая перед !, :, или же <LF> и назовите их VAR (может быть пустой список). Если VAR ломается раньше : и последующий символ ! затем включить : как последний символ в VAR и перерыв перед !
        • Если следующий символ ! затем
          • Если VAR существует, то
            замещать !VAR! со значением VAR и продолжить сканирование
          • Иначе если пакетный режим то
            Удалить !VAR! и продолжить сканирование
          • Остальное goto 5.2.1
        • Иначе, если следующий символ : затем
          • Если VAR не определен, то
            • Если пакетный режим то
              Удалить !VAR: и продолжить сканирование
            • Остальное goto 5.2.1
          • Иначе, если следующая строка символов соответствует шаблону
            ~[integer][,[integer]]! затем
            замещать !VAR:~[integer][,[integer]]! с подстрокой значения VAR (возможно, приводящей к пустой строке) и продолжить сканирование
          • Иначе, если следующая строка символов соответствует шаблону [*]search=[replace]! где поиск может включать в себя любой набор символов, кроме = а также <LF> и замена может включать любой набор символов, кроме ! а также <LF>, затем
            замещать !VAR:[*]search=[replace]! со значением VAR после выполнения поиска и замены (возможно, приводящего к пустой строке) и продолжения сканирования
          • Остальное goto 5.2.1
        • Остальное goto 5.2.1
      • 5.2.1
        • Если пакетный режим, то удалите !
          Остальное сохранить !
        • Продолжите сканирование, начиная со следующего символа после !

Как уже указывалось, командам передается вся строка аргумента в µSoft land, и они сами должны разбирать это на отдельные аргументы для собственного использования. В этом нет никакой согласованности между различными программами, и поэтому нет единого набора правил для описания этого процесса. Вам действительно нужно проверять каждый угловой случай на предмет того, какую библиотеку C использует ваша программа.

Насколько система .bat файлы идут, вот этот тест:

c> type args.cmd
@echo off
echo cmdcmdline:[%cmdcmdline%]
echo 0:[%0]
echo *:[%*]
set allargs=%*
if not defined allargs goto :eof
setlocal
@rem Wot about a nice for loop?
@rem Then we are in the land of delayedexpansion, !n!, call, etc.
@rem Plays havoc with args like %t%, a"b etc. ugh!
set n=1
:loop
    echo %n%:[%1]
    set /a n+=1
    shift
    set param=%1
    if defined param goto :loop
endlocal

Теперь мы можем запустить несколько тестов. Посмотрите, сможете ли вы выяснить, что именно пытается сделать µSoft:

C>args a b c
cmdcmdline:[cmd.exe ]
0:[args]
*:[a b c]
1:[a]
2:[b]
3:[c]

Хорошо пока. (Я оставлю неинтересным %cmdcmdline% а также %0 впредь.)

C>args *.*
*:[*.*]
1:[*.*]

Нет расширения имени файла.

C>args "a b" c
*:["a b" c]
1:["a b"]
2:[c]

Нет удаления кавычек, хотя кавычки предотвращают разбиение аргументов.

c>args ""a b" c
*:[""a b" c]
1:[""a]
2:[b" c]

Последовательные двойные кавычки приводят к тому, что они теряют все свои особые способности анализа. Пример @Beniot:

C>args "a """ b "" c"""
*:["a """ b "" c"""]
1:["a """]
2:[b]
3:[""]
4:[c"""]

Тест: как передать значение любого окружения var в качестве одного аргумента (то есть, как %1) в файл летучей мыши?

c>set t=a "b c
c>set t
t=a "b c
c>args %t%
1:[a]
2:["b c]
c>args "%t%"
1:["a "b]
2:[c"]
c>Aaaaaargh!

Разумный разбор кажется навсегда нарушенным.

Для вашего развлечения попробуйте добавить разное ^, \, ', & (& c.) символы для этих примеров.

У вас уже есть несколько отличных ответов, но вы можете ответить на одну часть вашего вопроса:

set a =b, echo %a %b% c% → bb c%

То, что там происходит, заключается в том, что, поскольку у вас есть пробел перед =, создается переменная с именем %a<space>%так что когда ты echo %a % это правильно оценивается как b,

Оставшаяся часть b% c% затем оценивается как простой текст + неопределенная переменная % c%, который должен быть отражен как напечатано, для меня echo %a %b% c% возвращается bb% c%

Я подозреваю, что возможность включать пробелы в имена переменных является скорее упущением, чем запланированной "функцией"

FOR-Расширение мета-переменной цикла

Это расширенное объяснение фазы 4) в принятом ответе (применимо как для режима пакетного файла, так и для режима командной строки). Конечно, команда должна быть активной. Далее описывается обработка части командной строки после doпункт. Обратите внимание, что в режиме пакетного файла %%уже был преобразован в из-за вышеупомянутой фазы немедленного расширения ( Фаза 1)).

  • сканировать -знак, начиная слева до конца строки; если таковой найден, то:
    • если расширения команд включены (по умолчанию), проверьте, является ли следующий символ ; если да, то:
      • взять как можно больше из следующих символов в наборе без учета регистра fdpnxsatz(даже несколько раз каждый), которые предшествуют символу, определяющему ссылку на переменную или -знак; если такой -знак встречается, то:
        • сканировать 1; если найдено, то:
          • если после символа есть символ, используйте его как ссылку на переменную и расширьте, как ожидалось, если только он не определен, то не расширяйте и продолжайте сканирование с этой позиции символа;
          • если это последний символ, cmd.exeрухнет!
        • иначе (не найдено) ничего не расширять;
      • иначе (если знак - не встречается) расширить переменную, используя все модификаторы, если только она не определена, затем не расширять и продолжить сканирование с этой позиции символа;
    • иначе (если не найдено или расширения команд отключены) проверьте следующий символ:
      • если больше нет доступного персонажа, ничего не расширяйте;
      • если следующий символ , ничего не расширять и вернуться к началу сканирования в позиции этого символа 2;
      • в противном случае используйте следующий символ в качестве ссылки на переменную и расширяйте его, если только он не определен, тогда не расширяйте;
  • вернуться к началу сканирования на позиции следующего символа (пока есть доступные символы);

1) Строка между $а также :рассматривается как имя переменной окружения, которое может быть даже пустым; поскольку переменная среды не может иметь пустое имя, поведение будет таким же, как и для неопределенной переменной среды.
2) Отсюда следует, что forмета-переменная с именем %не может быть расширен без ~-модификатор.


Исходный источник: Как безопасно повторить переменную FOR %%~p, за которой следует строковый литерал

Редактировать: см. принятый ответ, последующее неверно и объясняет только, как передать командную строку в TinyPerl.


Что касается цитат, у меня есть ощущение, что поведение следующее:

  • когда " найдено, начинается смещение строки
  • когда происходит разбивка строки:
    • каждый персонаж, который не является " сгущен
    • когда " найден:
      • если это сопровождается "" (таким образом, тройной ") затем в строку добавляется двойная кавычка
      • если это сопровождается " (таким образом, двойной ") затем к строке добавляется двойная кавычка, а завершение строки заканчивается
      • если следующий символ не ", конец строки
    • когда строка заканчивается, окантовка строки заканчивается.

Короче:

"a """ b "" c""" состоит из двух строк: a " b " а также c"

"a"", "a""" а также"a"""" все одинаковые строки, если в конце строки

Обратите внимание, что Microsoft опубликовала исходный код своего Терминала. Он может работать аналогично командной строке в отношении синтаксического анализа. Может быть, кто-то заинтересован в тестировании правил обратного синтаксического анализа на соответствие правилам синтаксического анализа терминала.

Ссылка на исходный код.

Другие вопросы по тегам