Как работает интерпретатор команд 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 в интерактивных сессий?
  • какие символы и в каком контексте? Как избежать знака процента? Например, как я могу echo %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 a=a" b а то echo.%a% получить a" b. Если я, однако, использую echo.exe от UnxUtils, я получаю a b. Как приходит %a% расширяется по-другому?

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

6 ответов


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

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

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

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

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

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

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

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

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

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

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

участок 5.3) обработка трубы: только если команды находятся по обе стороны трубы

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

фаза 6) обработка вызовов / удвоение каретки: только если маркер команды CALL

Фаза 7) Выполнить: команда выполняется


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

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

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

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

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

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

Фаза 1.5) Снять <CR>: удалить все возвраты каретки (0x0D) из строки

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

есть некоторые концепции, которые важны на протяжении всего этого этапа.

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

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

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

  • если это каретка (^), следующего символ экранируется, и убегающий каретка удаляется. Экранированные символы теряют все специальное значение (кроме <LF>).
  • если это цитата ("), переключить флаг цитата. Если флаг quote активен, то только " и <LF> особенная. Все остальные символы теряют свое особое значение, пока следующая цитата не отключит флаг цитаты. Невозможно избежать заключительной цитаты. Все указанные персонажи всегда в одном знак.
  • <LF> всегда выключает флаг цитата. Другое поведение зависит от контекста, но кавычки никогда не изменяют поведение <LF>.
    • бежал <LF>
      • <LF> лишен
      • следующий символ экранируется. Если в конце строки буфера, то следующая строка считывается и добавляется к текущей перед экранированием следующего символа. Если следующий символ <LF>, тогда он рассматривается как литерал, что означает этот процесс не является рекурсивным.
    • без эскапады <LF> не в скобках
      • <LF> разделяется и разбор текущей строки завершается.
      • любые оставшиеся символы в буфере строк просто игнорируются.
    • без эскапады <LF> в ДЛЯ в скобках блок
      • <LF> превращается в <space>
      • если в конце строки буфер, затем следующая строка читается и добавляется к текущей.
    • без эскапады <LF> в скобках командного блока
      • <LF> превращается в <LF><space> и <space> рассматривается как часть следующей строки командного блока.
      • если в конце буфера строки, то следующая строка считывается и добавляется к пространству.
  • если это один из специальных персонажи & | < или >, разделите линию в этой точке для обработки каналов, конкатенации команд и перенаправления.
    • в случае трубы (|), каждая сторона является отдельной командой (или командным блоком), которая получает специальную обработку в фазе 5.3
    • в случае &, && или || конкатенация команд, каждая сторона конкатенации рассматривается как отдельная команда.
    • в случае <, <<, > или >> redirection, предложение redirection анализируется, временно удаляется, а затем добавляется к концу текущей команды. Предложение перенаправления состоит из необязательной цифры дескриптора файла, оператора перенаправления и маркера назначения перенаправления.
      • если маркер, предшествующий оператору перенаправления, является одной цифрой, то цифра указывает дескриптор файла для перенаправления. Если маркер дескриптора не найден, выведите перенаправление по умолчанию 1 (stdout), а перенаправление ввода по умолчанию 0 (stdin).
  • если самый первый токен для этой команды (до перемещения перенаправления до конца) начинается с @, потом @ имеет особый смысл. (@ Не спец в любом другом контексте)
    • специальный @ удалены.
    • если ECHO включен, то эта команда вместе с любыми следующими объединенными командами на этом линии, исключаются из фазы 3 Эхо. Если @ перед открытием (, затем весь блок в скобках исключается из фазы 3 echo.
  • скобки процесса (обеспечивает для составных операторов через несколько строк):
    • если синтаксический анализатор не ищет маркер команды, то ( это не специально.
    • если парсер ищет маркер команды и находит (, затем запустите новое соединение утверждение и инкремент счетчика скобок
    • если счетчик скобок > 0, то ) завершает составную инструкцию и уменьшает счетчик скобок.
    • если достигнут конец строки и счетчик скобок > 0, то следующая строка будет добавлена к составному оператору (начинается снова с Фазы 0)
    • если счетчик скобок равен 0 и анализатор ищет команду, то ) функции, аналогичные a REM оператор, если за ним немедленно следует разделитель токенов, специальный символ, новая строка или конец файла
      • все специальные символы теряют свое значение, за исключением ^ (возможна конкатенация строк)
      • как только достигнут конец логической строки, вся "команда" отбрасывается.
  • каждая команда разбивается на ряд лексем. Первый токен всегда рассматривается как команда маркер (после @ были удалены и перенаправление перемещено в конец).
    • ведущие разделители маркеров до маркера команды удаляются
    • при разборе маркера команды,( функции в качестве разделителя маркеров команд, в дополнение к стандартным разделителям маркеров
    • обработка последующих токенов зависит от команды.
  • большинство команд просто объединяют все аргументы после маркер команды в маркер одного аргумента. Все разделители маркеров аргументов сохраняются. Параметры аргументов обычно не анализируются до фазы 7.
  • три команды получают специальную обработку-IF, FOR и REM
    • IF разбивается на две или три отдельные части, которые обрабатываются независимо. Синтаксическая ошибка в конструкции IF приведет к фатальной синтаксической ошибке.
      • операция сравнения-это фактическая команда, которая проходит весь путь на 7 этапе
        • все, если параметры полностью проанализированы в фазе 2.
        • последовательные разделители токенов сворачиваются в одно пространство.
        • в зависимости от оператора сравнения будет один или два маркера значения, которые идентифицируются.
      • истинный командный блок-это набор команд после условия и анализируется как любой другой командный блок. Если используется ELSE, то блок True должен быть в скобках.
      • необязательный блок False command-это набор команд после ELSE. Опять же, этот командный блок анализируется нормально.
      • блоки команд True и False не переходят автоматически в последующие фазы. Их последующая обработка контролируется фазой 7.
    • FOR разделяется на два после того, как сделать. Синтаксическая ошибка в конструкции приведет к фатальной ошибке.
      • часть через DO-это фактическая команда для итерации, которая проходит весь путь через фазу 7
        • все параметры полностью проанализированы в фазе 2.
        • в скобках предложение лечит <LF> as <space>. После анализа предложения IN все маркеры объединяются вместе, чтобы сформировать один маркер.
        • последовательные разделители маркеров unescaped/unquoted сворачиваются в одно пространство по всей команде FOR через ДЕЛАТЬ.
      • часть после DO-это командный блок, который анализируется нормально. Последующая обработка блока команд DO контролируется итерацией в фазе 7.
    • REM, обнаруженный в фазе 2, обрабатывается значительно иначе, чем все другие команды.
      • анализируется только один маркер аргумента-синтаксический анализатор игнорирует символы после первого маркера аргумента.
      • если есть только один аргумент маркер, который заканчивается сразу после ^ это завершает строку, затем маркер аргумента выбрасывается, а последующая строка анализируется и добавляется к REM. Это повторяется до тех пор, пока не будет более одного токена, или последний символ не ^.
      • команда REM может появиться на выходе фазы 3, но команда никогда не выполняется, и исходный текст аргумента повторяется - экранирующие каретки не удаляются.
  • если маркер команды начинается с :, и это первый раунд фазы 2 (не перезапуск из-за вызова в фазе 6), затем
    • маркер обычно рассматривается как Unexecuted Label.
      • остаток строки анализируется, однако ), <, >, & и | больше не имеют особого значения. Вся остальная часть строки считается частью метки "command".
      • на ^ продолжает быть особенным, что означает, что продолжение строки может использоваться для добавления последующей строки к метке.
      • An Unexecuted Label в круглых скобках блок приведет к фатальной синтаксической ошибке, если за ним немедленно не последует команда или Выполнена Метка на следующей строке.
        • обратите внимание, что ( больше не имеет специального значения для первой команды, которая следует за Unexecuted Label в этом контекст.
      • команда прерывается после завершения синтаксического анализа метки. Последующие фазы для метки
    • есть три исключения, которые могут привести к тому, что метка, найденная в фазе 2, будет рассматриваться как Выполнена Метка это продолжает анализ через фазу 7.
      • есть перенаправление, которое предшествует маркеру метки, и есть | труба или &, &&, или || конкатенация команд в строке.
      • существует перенаправление, которое предшествует маркеру метки, и команда находится в скобках блока.
      • маркер метки является самой первой командой в строке внутри блока в скобках, а строка выше заканчивается Unexecuted Label.
    • следующее происходит, когда Выполнена Метка обнаружен в фазе 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 иметь приоритет над именами переменных. Если персонаж, следующий ~ является модификатором и допустимым для имени переменной, и существует последующий символ, который является активным для имени переменной, тогда символ интерпретируется как модификатор.
  • для имен переменных являются глобальными, но только в контексте предложения DO. Если подпрограмма вызывается из предложения for DO, переменные FOR не расширяются в вызываемой подпрограмме. Но если у рутины есть своя команда, то все в настоящее время определенные для переменных доступны для внутренних команд DO.
  • для имен переменных можно повторно использовать во вложенных FORs. Внутренний для значения имеет приоритет, но как только внутренний для закрывается, то внешний для значения восстанавливается.
  • если эхо было включено в начале этой фазы, то фаза 3) повторяется, чтобы показать проанализированные команды DO после того, как переменные были расширенный.

- - - - - с этого момента каждая команда, идентифицированная в фазе 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) обработка вызовов / удвоение каретки: только если маркер команды является вызовом или если текст перед первым встречающимся стандартным разделителем маркеров является вызовом. Если вызов анализируется из большего маркера команды, то неиспользуемая часть добавляется к маркеру аргументов перед процессуальное действие.

  • сканировать маркер аргументов для unquoted /?. Если найдено где-либо в пределах токенов, то прервите фазу 6 и перейдите к фазе 7, где будет напечатана справка для вызова.
  • удалить первый элемент CALL, так что несколько вызовов могут быть сложены
  • двойные крышки
  • перезагрузка фаз 1, 1,5 и 2, но не 3 фазы
    • все удвоенные каретки уменьшены назад к одному каретке покуда поскольку они не цитируются. Но, к сожалению, котировки остаются удвоенными.
    • кэшированный командный блок уже подготовлен в исходном раунде фазы 2. Поэтому многие задачи фазы 2 изменены
      • обнаруживается любое вновь появляющееся неквотируемое перенаправление, которое не было обнаружено в первом раунде фазы 2, но оно удаляется (включая имя файла) без фактического выполнения перенаправления
      • все вновь появляющиеся без кавычек, unescaped каретка в конце строки удаляется без выполнения продолжения строки
      • вызов прерывается без ошибок, если обнаружено одно из следующих
        • новое появление без кавычек, без эскапады & или |
        • результирующий маркер команды начинается с unquoted, unescaped (
        • самый первый токен после удаленного вызова начался с @
      • если равнодействующая команда, по-видимому, действительна, если или Для, то выполнение впоследствии завершится ошибкой с сообщением, что IF или FOR не распознается как внутренней или внешней командой.
      • конечно, вызов не прерывается в этом 2-м раунде фазы 2, Если результирующий маркер команды-это метка, начинающаяся с :.
    • есть некоторые крайние случаи, когда эти правила терпят неудачу:
      См.рассмотрение Linefeeds с Звоните
  • если результирующий токен команды-CALL, то перезапустите фазу 6 (повторяется до тех пор, пока больше не вызовет)
  • если результирующий маркер команды представляет собой пакетный скрипт или метку:, то выполнение вызова полностью обрабатывается оставшейся частью фазы 6.
    • нажмите текущую позицию файла пакетного сценария в стеке вызовов, чтобы по завершении вызова можно было возобновить выполнение из правильной позиции.
    • настройка в %0, %1, %2, ...%N и %* маркеры аргументов для вызова, используя все результирующие маркеры
    • если маркер команды-это метка, начинающаяся с :, тогда
      • Перезапустить Этап 5. Это может повлиять на то, что называется: label. Но с %0 и т. д. токены уже настроены, это не изменит аргументы, которые передаются вызываемой подпрограмме.
      • выполнить метку GOTO для размещения указателя файла в начале подпрограммы (игнорировать любые другие маркеры, которые могут следовать: label) см. фазу 7 для правил о том, как работает GOTO.
    • Else передать управление в указанный пакетный скрипт.
    • выполнение вызываемой: метки или скрипта продолжается до тех пор, пока не будет достигнут выход /B или конец файла, в этот момент стек вызовов выскакивает и выполнение возобновляется из сохраненного файла позиция.
      Фаза 7 не выполняется для вызываемых скриптов или: labels.
  • иначе результат фазы 6 попадает в фазу 7 для выполнения.

Фаза 7) Выполнить: выполняется команда

  • 7.1 - выполнить внутреннюю команду - если указан маркер команды, пропустите этот шаг. В противном случае попытайтесь проанализировать внутреннюю команду и выполнять.
    • следующие тесты выполняются, чтобы определить, представляет ли маркер команды без кавычек внутреннюю команду:
      • если маркер команды точно соответствует внутренней команде,выполните ее.
      • иначе сломайте маркер команды до первого появления + / [ ] <space> <tab> , ; или =
        Если предыдущий текст является внутренней командой, то помните, что команда
        • если в режиме командной строки, или если команда из блока в скобках, если true или false командный блок, для do командный блок, или участвует в конкатенации команд, то выполните внутреннюю команду
        • Else (должна быть автономной командой в пакетном режиме) сканировать текущую папку и путь для a .COM,.ИСПОЛНЯЕМЫЙ. ,летучая мышь или. CMD-файл, базовое имя которого соответствует исходному маркеру команды
          • если первый соответствующий файл a .летучая мышь или. УМК, затем Гото 7.3.exec и выполнить этот скрипт
          • Else (матч не найден или первый матч .EXE или .COM) выполните запомнившуюся внутреннюю команду
      • иначе сломайте маркер команды до первого появления . \ или :
        Если предыдущий текст не является внутренней командой, то goto 7.2
        Еще предыдущий текст может быть внутренней командой. Помнить об этом команда.
      • сломать маркер команды до первого появления + / [ ] <space> <tab> , ; или =
        Если предыдущий текст является путем к существующему файлу, то goto 7.2
        Еще вспомнил выполнить внутреннюю команду.
    • если внутренняя команда анализируется из большего маркера команды, то неиспользуемая часть маркера команды включается в аргумент список
    • только потому, что токен команды анализируется как внутренняя команда, не означает, что он будет успешно выполняться. Каждая внутренняя команда имеет свои собственные правила относительно того, как анализируются Аргументы и параметры и какой синтаксис разрешен.
    • все внутренние команды будут печатать справку вместо выполнения своей функции, если /? обнаружено. Большинство признают /? если он появляется в любом месте аргументов. Но несколько команд, таких как ECHO и SET, только печатают помощь, если первый аргумент token начинается с /?.
    • набор имеет интересную семантику:
      • если команда SET имеет кавычку перед именем переменной
        set "name=content" ignored --> value=content
        затем в качестве содержимого используется текст между первым знаком равенства и последней кавычкой (первая и последняя кавычки исключены). Текст после последней цитаты игнорируется. Если после знака равенства нет кавычки, то остальная часть строки используется как содержание.
      • если команда SET не имеет кавычки перед именем
        set name="content" not ignored --> value="content" not ignored
        затем весь остаток строки после равного используется в качестве содержимого, включая любые и все предложения, которые могут присутствовать.
    • если сравнение оценивается, и в зависимости от того, является ли условие истинным или ложным, соответствующий уже проанализированный зависимый командный блок обрабатывается, начиная с фазы 5.
    • предложение IN команды FOR повторяется соответствующим образом.
      • если это FOR / F, который повторяет вывод командного блока, то:
        • предложение IN выполняется в новом cmd.exe процесс через CMD / C.
        • командный блок должен пройти весь процесс синтаксического анализа во второй раз, но на этот раз в контексте командной строки
        • Эхо начнется, и отложенное расширение, как правило, начнет отключаться (зависит от параметра реестра)
        • все изменения среды, внесенные в командный блок предложения IN, будут потеряны после дочернего cmd.exe процесс завершается
      • для каждой итерации:
        • значения переменных for определены
        • затем обрабатывается уже проанализированный блок команд DO, начиная с ФАЗЫ 4.
    • GOTO использует следующую логику для обнаружения :этикетка
      • метка анализируется из первого аргумента token
      • скрипт сканируется для следующего вхождения метки
        • сканирование начинается с текущей позиции файла
        • если достигнут конец файла, то сканирование возвращается к началу файла и продолжается до исходной точки.
      • сканирование останавливается при первом появлении метки, которую он находит, и указатель файла устанавливается на строку, непосредственно следующую за меткой. Выполнение сценария возобновляется с этого момента. Обратите внимание, что успешный true GOTO немедленно прервет любой проанализированный блок кода, в том числе для циклов.
      • если метка не найдена, или маркер метки отсутствует, то GOTO терпит неудачу, сообщение об ошибке печатается, и стек вызовов выскакивает. Это эффективно функционирует как EXIT/ B, за исключением любых уже проанализированных команд в текущем блоке команд, которые следуют Гото все еще выполняются, но в контексте вызывающего абонента (контекст, который существует после выхода /B)
      • см.https://www.dostips.com/forum/viewtopic.php?f=3&t=3803 для более точного описания правил, используемых для разбора меток.
    • переименовать и скопировать оба принимают подстановочные знаки для исходного и целевого путей. Но Microsoft делает ужасную работу, документируя, как работают подстановочные знаки, особенно для целевого пути. Полезный набор подстановочных правил можно найти в как команда переименования Windows интерпретирует подстановочные знаки?
  • 7.2 - выполнять изменение громкости - Else если маркер команды не начинается с кавычки, имеет длину ровно два символа, а 2-й символ-двоеточие, то измените громкость
    • все маркеры аргументов игнорируются
    • если том, указанный первым символом, не может быть найден, то прервать с ошибкой
    • команда знак :: всегда приведет к ошибке, если SUBST не используется для определения тома для ::
      Если SUBST используется для определения тома для ::, тогда том будет изменен, он не будет обработан как ярлык.
  • 7.3 - выполнить внешнюю команду - еще попробуйте обработать команду как внешнюю команду.
    • если 2-й символ маркера команды является двоеточие, затем проверьте, что Том, указанный 1-м символом, может быть найден.
      Если том не найден, прервите работу с ошибкой.
    • если в пакетном режиме и маркер команды начинается с :, затем перейти 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) Отложенное Расширение: только если DelayedExpansion включен

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

Фаза 7) Выполнить Команду

  • попытки вызова или 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 где вопросы могут быть представлены и предложения.

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


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

если это звучит как анархия, так и есть. Однако, так как большое количество программ Windows do использовать Microsoft C / C++ runtime в argv, это может быть полезным, чтобы понять как библиотекой размечает аргументов. Вот отрывок:--10-->

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

пакет Microsoft язык" (.bat) не является исключением из этой анархической среды, и она разработала свои собственные уникальные правила для токенизации и побега. Это также выглядит как cmd.командная строка exe выполняет некоторую предварительную обработку аргумента командной строки (в основном для подстановки переменных и экранирования) перед передачей аргумента новому исполняемому процессу. Вы можете прочитать больше о низкоуровневых деталях пакетного языка и cmd escaping в отличных ответах 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 (расширить аргумент) пропустить, если режим командной строки
    • Else если пакетный режим, то
      • если последует * и расширения команд включены тогда
        Заменить %* С текстом всех аргументов командной строки (заменить ничем, если нет аргументов) и продолжить сканирование.
      • Else если следовать <digit> тогда
        Заменить %<digit> со значением аргумента (заменить ничем, если не определено) и продолжить сканирование.
      • Else если следовать ~ и расширения команд включены
        • если за необязательным допустимым списком модификаторов аргументов следует обязательный <digit> затем
          Заменить %~[modifiers]<digit> с измененным значением аргумента (заменить ничем, если не определено или если указано $PATH: модификатор не определен) и продолжить сканирование.
          Примечание.: модификаторы нечувствительны к регистру и могут появляться несколько раз в любом порядке, кроме $PATH: модификатор может появляться только один раз и должен быть последним модификатором перед <digit>
        • Else недопустимый измененный синтаксис аргумента вызывает фатальная ошибка: все проанализированные команды прерываются, и пакетная обработка прерывается, если в пакетном режиме!
  • 1.3 (расширить переменной)
    • Else если расширения команд отключены, то
      Посмотрите на следующую строку символов, ломая перед % или <LF>, и назовите их VAR (может быть пустой список)
      • если следующий символ % затем
        • если var определен, то
          Заменить %VAR% со значением VAR и продолжить сканирование
        • еще если пакетный режим, то
          Удалить %VAR% и продолжить сканирование
        • Else goto 1.4
      • еще Гото 1.4
    • Else если расширения команд включены, то
      Посмотрите на следующую строку символов, ломая перед % : или <LF>, и назовите их VAR (может быть пустой список). Если var ломается до : и последующий символ включить : как последний символ в VAR и перерыв перед %.
      • если следующий символ % затем
        • если var определен, то
          Заменить %VAR% со значением VAR и продолжить сканирование
        • еще если пакетный режим, то
          Удалить %VAR% и продолжить сканирование
        • еще Гото 1.4
      • Else, если следующий символ : затем
        • если VAR не определен, то
          • если пакетный режим, то
            Удалить %VAR: и продолжить сканирование.
          • Else goto 1.4
        • Else, если следующий символ ~ затем
          • если следующая строка символов соответствует шаблону [integer][,[integer]]% затем
            Заменить %VAR:~[integer][,[integer]]% С подстрокой значения VAR (возможно, в результате пустой строки) и продолжить сканирование.
          • еще Гото 1.4
        • Else если следовать = или *= затем
          Недопустимый синтаксис поиска и замены переменных вызывает смертельный ошибка: все проанализированные команды прерываются, и пакетная обработка прерывается, если в пакетном режиме!
        • Else, если следующая строка символов соответствует шаблону [*]search=[replace]%, где поиск может включать любой набор символов, кроме = и <LF>, и замена может включать любой набор символов, кроме % и <LF>, затем заменить
          %VAR:[*]search=[replace]% со значением VAR после выполнения поиска и замены (возможно, в результате пустой строки) и продолжить scan
        • еще Гото 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 - интересный результат ордена правила разбора, которые джеб излагает в своем посте: при выполнении поиска и замены обычным расширением специальные символы не должны экранироваться (хотя они могут быть процитированы). Но при выполнении поиска и замены отложенным расширением специальные символы должны быть экранированы (если они не указаны).

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

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

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

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

  • 5.1 (каре побег) нужен для ! или ^ константы
    • если символ является кареткой ^ затем
      • удалить ^
      • сканируйте следующий символ и сохраните его как литерал
      • продолжить сканирование
  • 5.2 (расширить переменной)
    • если символ !, тогда
      • если расширения команд отключены, то
        Посмотрите на следующую строку символов, ломающихся перед ! или <LF>, и назовите их VAR (может быть пустой список)
        • если следующий символ ! затем
          • если var определен, то
            Заменить !VAR! со значением VAR и продолжить сканирование
          • еще если пакетный режим, то
            Удалить !VAR! и продолжить сканирование
          • Else goto 5.2.1
        • Else goto 5.2.1
      • Else, если включены расширения команд тогда
        Посмотрите на следующую строку символов, ломая перед !, : или <LF>, и назовите их VAR (может быть пустой список). Если var ломается до : и последующий символ включить : как последний символ в VAR и перерыв перед !
        • если следующий символ ! затем
          • если VAR существует, то
            Заменить !VAR! со значением VAR и продолжить сканирование
          • Else если пакетный режим тогда
            Удалить !VAR! и продолжить сканирование
          • Else goto 5.2.1
        • Else, если следующий символ : затем
          • если VAR не определен, то
            • если пакетный режим, то
              Удалить !VAR: и продолжить сканирование
            • Else goto 5.2.1
          • Else, если следующая строка символов соответствует шаблону
            ~[integer][,[integer]]! затем
            Заменить !VAR:~[integer][,[integer]]! с подстрока значения VAR (возможно, в результате пустой строки) и продолжить сканирование
          • Else, если следующая строка символов соответствует шаблону [*]search=[replace]!, где поиск может включать любой набор символов, кроме = и <LF>, и замена может включать любой набор символов, кроме ! и <LF>, потом
            Заменить !VAR:[*]search=[replace]! со значением VAR после выполнения поиска и замены (возможно, в результате пустой строки) и продолжить сканирование
          • Else goto 5.2.1
        • Else 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 как один аргумент (т. е. as %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.) характеры к этим примерам.


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

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

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

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

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


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


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

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

короче:

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

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