Как интерпретатор команд Windows (CMD.EXE) анализирует сценарии? - PullRequest
125 голосов
/ 04 ноября 2010

Я столкнулся с 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 в интерактивных сеансах?
  • Что такое escape-символы и в каком контексте?Как избежать знака процента?Например, как я могу буквально повторить %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% расширяется по-другому?

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

Ответы [ 6 ]

166 голосов
/ 04 ноября 2010

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

Анализатор линии партии:

Вот краткий обзор этапов обработки строки кода в командном файле:

Фаза 0) Строка считывания:

Фаза 1) Процентное расширение:

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

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

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

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

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

Фаза 5.3) Обработка трубы: Только если команды находятся на любой стороне трубы

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

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

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


Вот подробности для каждой фазы:

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

Фаза 0) Считать строку: Считать строку ввода через первый <LF>.

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

Фаза 1) Процентное расширение:

  • Двойной %% заменяется одним %
  • Расширение аргументов (%*, %1, %2 и т. Д.)
  • Расширение %var%, если var не существует, заменить его ничем
  • Строка сначала усекается <LF> не в пределах %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> убран
      • Следующий символ экранирован.Если в конце буфера строки, следующая строка считывается и обрабатывается фазами 1 и 1.5 и добавляется к текущей перед экранированием следующего символа.Если следующий символ - <LF>, то он обрабатывается как литерал, что означает, что этот процесс не является рекурсивным.
    • Без экранирования <LF> не в скобках
      • <LF> удаляется и анализ текущей строки прекращается.
      • Все оставшиеся символы в буфере строки просто игнорируются.
    • Unescaped <LF> в FOR 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 приведет к фатальной синтаксической ошибке.
      • Операция сравнения - это действительная команда, которая проходит всю фазу до фазы 7
        • Все опции IF полностью проанализированы в фазе 2.
        • Последовательные разделители маркеров сворачиваются в одно пространство.
        • В зависимости от оператора сравнения могут быть определены один или два токена значения.
      • Блок команд True - это набор команд после условия, который анализируется, как и любой другой блок команд. Если нужно использовать ELSE, тогда блок True должен быть заключен в скобки.
      • Необязательный блок команд False - это набор команд после 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 (не перезапуск из-за ВЫЗОВА в фазе 6), тогда
    • Маркер обычно обрабатывается как Неисполненная метка .
      • Остальная часть строки анализируется, однако ), <, >, & и | больше не имеют специального значения.Весь остаток строки считается частью метки «команда».
      • ^ продолжает быть особенным, то есть продолжение строки может использоваться для добавления следующей строки к метке.
      • Неисполненная метка в заключенном в скобки блоке приведет к фатальной синтаксической ошибке, если сразу за ней не последует команда или Executed Label в следующей строке.
        • Обратите внимание, что ( больше не имеет особого значения для первой команды, следующей за неисполненной меткой в этом контексте.
      • Командапрервано после завершения анализа метки.Последующие этапы не выполняются для метки
    • . Существуют три исключения, которые могут привести к тому, что метка, обнаруженная на этапе 2, будет рассматриваться как Executed Label , которая продолжает анализдо фазы 7.
      • Существует перенаправление, которое предшествует токену метки, и существует | труба или &, && или || конкатенация команд на линии.
      • Существует перенаправление, которое предшествует токену метки, и команда находится в блоке в скобках.
      • Маркер метки является самой первой командой в строке в блоке в скобках, и строка выше заканчивалась Неисполненная метка .
    • Следующее происходит, когда Исполняемая метка обнаруживается на этапе 2
      • Метка, ее аргументы,и его перенаправление все исключены из любого эхо-выхода в фазе 3
      • Все последующие составные команды в строке полностью анализируются и выполняются.
    • Для получения дополнительной информацииинформация о Выполненные метки против Неисполненные метки , см. https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405

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

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

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

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

Этап 5) Отложенное расширение: Только если включено отложенное расширение, команда не находится в блоке с круглыми скобками по обе стороны канала , и команда не является «голый» пакетный сценарий (имя сценария без скобок, CALL, конкатенация команд или канал).

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

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

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

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

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

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

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

  • 7.1 - Выполнить внутреннюю команду - Если токен команды указан в кавычках, пропустить этошаг.В противном случае попытайтесь разобрать внутреннюю команду и выполнить.
    • Следующие тесты выполняются, чтобы определить, представляет ли токен команды без кавычек внутреннюю команду:
      • Если токен команды точно соответствует внутренней команде, то выполнить ее.
      • Иначеразбить маркер команды до первого появления + / [ ] <space> <tab> , ; или =
        Если предыдущий текст является внутренней командой, топомните, что команда
        • Если она находится в режиме командной строки, или если команда из блока, заключенного в скобки, командного блока ЕСЛИ истина или ложь, командного блока FOR DO или связана с объединением команд, то выполните внутреннюю команду
        • В противном случае (должна быть автономной командой в пакетном режиме) сканировать текущую папку и путь к файлу .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 -> value = content
        , затем текст между первым знаком равенства и последней цитатой используется в качестве содержимого (первое равно и последнеецитата исключена).Текст после последней цитаты игнорируется.Если после знака равенства нет кавычек, то остальная часть строки используется в качестве содержимого.
      • Если команда SET не имеет кавычек перед именем
        set name="content" not ignored -> value = "content" not ignored
        тогда весь остаток строки после равенства используется в качестве содержимого, включая любые и все кавычки, которые могут присутствовать.
    • AnЕсли выполняется сравнение IF, и в зависимости от того, является ли условие истинным или ложным, обрабатывается соответствующий уже проанализированный зависимый блок команд, начиная с фазы 5.
    • Предложение IN команды FOR повторяется соответствующим образом.
      • Если это FOR / F, который повторяет вывод командного блока, тогда:
        • Предложение IN выполняется в новом процессе cmd.exe через CMD /C.
        • Командный блок должен пройти весь процесс синтаксического анализа во второй раз, но на этот раз в контексте командной строки
        • ECHO запустится ON, а отложенное расширение, как правило, будет отключено (зависит от реестра
        • Все изменения среды, сделанные с помощью командного блока предложения IN, будут потеряны после завершения дочернего процесса cmd.exe
      • Для каждой итерации:
        • Определены значения переменных FOR
        • Затем обрабатывается уже проанализированный командный блок DO, начиная с фазы 4.
    • GOTO использует следующееЛогика для поиска: label
      • Метка анализируется по первому аргументу token
      • Сценарий сканируется на предмет следующего появления метки
        • Сканирование начинается с текущегофайл рosition
        • Если достигнут конец файла, то сканирование возвращается к началу файла и продолжается до исходной начальной точки.
      • Сканирование останавливается при первом появлении найденной метки, а указатель файла устанавливается на строку, следующую непосредственно за меткой.Выполнение сценария возобновляется с этого момента.Обратите внимание, что успешное истинное 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 - Выполнить внешнюю команду -Еще попробуйте воспринимать команду как внешнюю команду.
    • Если в режиме командной строки команда не указана в кавычках и не начинается со спецификации тома, то при первом появлении <space> , ; или = идобавьте остаток к токену (ам) аргумента.
    • Если вторым символом токена команды является двоеточие, убедитесь, что можно найти том, указанный в первом символе.
      Если том не может бытьнайдено, затем прервано с ошибкой.
    • Если в пакетном режиме токен команды начинается с :, то перейдите к 7.4
      Обратите внимание, что если маркер метки начинается с ::, то это не будетбудет достигнут, потому что предыдущий шаг будет прерван с ошибкой, если SUBST не используется для определения тома для ::.
    • Определите внешнюю команду для выполнения.
      • Это сложный процесс, который может включать в себя текущий том, текущий каталог, переменную PATH, переменную PATHEXT и / или сопоставления файлов.
      • Если допустимая внешняя команда не может быть идентифицирована, прервать ее с помощьюошибка.
    • Если в режиме командной строки токен команды начинается с :, то переходите к 7.4
      Обратите внимание, что это редко достигается, поскольку предыдущий шаг будет прерван с помощьюошибка, если токен команды не начинается с ::, а SUBST используется для определения тома для ::, а весь токен команды является допустимым путем к внешней команде.
    • 7.3.exec - выполнить внешнюю команду.
  • 7.4 - игнорировать метку - игнорировать команду и все ее аргументы, если маркер команды начинается с :.
    Правила в 7.2 и 7.3 могут препятствовать достижению меткой этой точки.

Анализатор командной строки:

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

Фаза 1) Процентное расширение:

  • Нет %*, %1 и т. Д. Расширение аргумента
  • Если var не определен, то %var% остаетсябез изменений.
  • Нет специальной обработки %%.Если var = content, то %%var%% расширяется до %content%.

Этап 3) Выводит проанализированные команды (ы)

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

Этап 5) Отсроченное расширение: только если DelayedExpansion имеет значениеenabled

  • Если 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 анализирует числа


Для всех желающихДля улучшения правил разбора cmd.exe на форуме DosTips есть тема для обсуждения *1932*, где можно сообщать о проблемах и вносить предложения.

Надеюсь, что это поможет
Ян Эрик (jeb)- Оригинальный автор и первооткрыватель фаз
Дейв Бенхэм (dbenham) - Много дополнительного контента и редактирования

59 голосов
/ 04 ноября 2010

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

Если это звучит как анархия, так и есть. Однако, поскольку большое количество программ для Windows do использует argv среды выполнения Microsoft C / C ++, в целом может быть полезно понять , как 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]
43 голосов
/ 01 ноября 2011

Правила расширения процента

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

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

  • 1,05 (усеченная строка в <LF>)
    • Если символ <LF>, то
      • Удалить (игнорировать) остаток строки от <LF> и далее
      • Перейти к Фазе 1.5 (Strip <CR>)
    • Иначе символ должен быть %, поэтому продолжайтедо 1.1
  • 1.1 (escape %) пропускается, если режим командной строки
    • Если используется пакетный режим и затем следуетдругой % затем
      Замените %% на один % и продолжите сканирование
  • 1.2 (аргумент раскрытия) пропускается, если в командной строкеmode
    • Иначе, если в пакетном режиме то
      • Если за ним следуют * и расширения команд включены, тогда
        Заменить %* текстом всех аргументов командной строки (Заменитьни с чем, если нет аргументов) и продолжить сканирование.
      • Иначе, если следуют <digit>, затем
        Заменить %<digit> значением аргумента (заменить ничем, если не определено) и продолжить сканирование.
      • Иначе, если за ним следуют ~ и командатогда включаются расширения
        • Если за ним следует необязательный действительный список модификаторов аргумента, за которым следуют обязательные <digit> затем
          Замените %~[modifiers]<digit> измененным значением аргумента (замените ничем, если не определено или если указано $ PATH:модификатор не определен) и продолжить сканирование.
          Примечание: модификаторы чувствительны к регистру и могут появляться несколько раз в любом порядке, кроме $ PATH: модификатор может появляться только один раз и должен быть последним модификатором перед <digit>
        • В противном случае повышается неверный синтаксис измененного аргумента фатальная ошибка: все проанализированные команды отменяются, а пакетная обработка прерывается, если в пакетном режиме!
  • 1.3 (раскрываемая переменная)
    • Иначе, если расширения команд отключены, тогда
      Посмотрите на next строка символов, прерывающаяся перед % или концом буфера, и называется их VAR (может быть пустой список)
      • Если следующий символ %, то
        • Если определен VARзатем
          Замените %VAR% значением VAR и продолжите сканирование
        • В противном случае, если в пакетном режиме, тогда
          Удалите %VAR% и продолжите сканирование
        • Иначе перейдите к 1,4
      • Иначе перейти к 1.4
    • Иначе, если включены расширения команд, тогда
      Посмотрите на следующую строку символов, разбив до % : или до концабуфера, и назовите их VAR (может быть пустой список).Если VAR прерывается до :, а последующий символ %, тогда включите : в качестве последнего символа в VAR и разбейте до %.
      • Если следующий символ %, то
        • Если определен VAR, тогда
          Заменить %VAR% значением VAR и продолжить сканирование
        • Иначе, если в пакетном режиме тогда
          Удалить %VAR% и продолжить сканирование
        • Иначе перейти к 1.4
      • Иначе, если следующий символ :, тогда
        • Если VAR не определензатем
          • Если в пакетном режиме, то
            Удалите %VAR: и продолжите сканирование.
          • Иначе перейти к 1.4
        • Иначе, если следующий символ ~, тогда
          • Если следующая строка символов соответствует шаблону [integer][,[integer]]%, то
            Замените %VAR:~[integer][,[integer]]% подстрокой значения VAR (возможно, результатом будет пустая строка) и продолжите сканирование.
          • Иначе перейдите к 1,4
        • Иначе, если за ним следует =или *= затем
          Возникает неверный поиск и замена синтаксиса фатальная ошибка: все проанализированные команды прерываются, а пакетная обработка прерывается, если в пакетном режиме!
        • Иначе, если следующая строка символов соответствует шаблону [*]search=[replace]%, где поиск может включать любой набор символов, кроме =, а замена может включать любой набор символов, кроме %, затем заменить
          %VAR:[*]search=[replace]%со значением VAR после выполнения поиска и замены (возможно, приводящего к пустой строке) и продолжения сканирования
        • Иначе перейти к 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 - еще одно интересное следствие правил синтаксического анализа%: переменные, содержащие: в имени, могут быть определены, но их нельзя развернуть, если только командаРасширения отключены.Есть одно исключение - имя переменной, содержащее один двоеточие в конце, может быть расширено, если включены расширения команд.Однако вы не можете выполнять подстроку или операции поиска и замены над именами переменных, заканчивающимися двоеточием.Пакетный файл ниже (любезно предоставлен 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 - Интересный результат порядка правил синтаксического анализа, который выкладывает Jebв своем посте: При выполнении поиска и замены обычным расширением НЕ ДОЛЖНЫ экранироваться специальные символы (хотя они могут быть заключены в кавычки).Но при выполнении поиска и замены с отложенным расширением ДОЛЖНЫ экранироваться специальные символы (если они не заключены в кавычки).

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

Правила отложенного расширения

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

фаза 5) расширение с задержкой

эта фазапропускается, если выполняется любое из следующих условий:

  • Отложенное расширение отключено.
  • Команда находится в заключенном в скобки блоке по обе стороны канала.
  • Токен входящей команды является «пустым» пакетным сценарием, то есть он не связан с CALL, блоком в скобках, какой-либо формой объединения команд (&, && или ||) или каналом |.

Процесс отложенного расширения применяется к токенам независимо.У команды может быть несколько токенов:

  • Токен команды.Для большинства команд само имя команды является токеном.Но некоторые команды имеют специализированные области, которые считаются символом для фазы 5.
    • for ... in(TOKEN) do
    • if defined TOKEN
    • if exists TOKEN
    • if errorlevel TOKEN
    • if cmdextversion TOKEN
    • if TOKEN comparison TOKEN, где сравнение является одним из ==, equ, neq, lss, leq, gtr,или geq
  • Маркер аргументов
  • Маркер назначения перенаправления (по одному на перенаправление)

В токены не вносятся изменениякоторые не содержат !.

Для каждого токена, который содержит хотя бы один !, отсканируйте каждый символ слева направо на предмет ^ или !, и, если он найден, то

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

Как уже указывалось, командам передается вся строка аргумента в µ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"""]

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

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.).

5 голосов
/ 12 августа 2014

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

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

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

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

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

0 голосов
/ 04 ноября 2010

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


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

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

Короче говоря:

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

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

...