Как работает интерпретатор команд Windows (CMD.EXE) разбирать скрипты?



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



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



вот примеры вопросов, которые я не смог решить:




  • как управляется система котировок? Я сделал TinyPerl скрипт

    (foreach $i (@ARGV) { print '*' . $i ; }), скомпилировал его и назвал так :



    • my_script.exe "a ""b"" c" выход → составляет *a "b*c


    • my_script.exe """a b c""" выход → это *"a*b*c"



  • как работает внутренний echo командная работа? Что расширяется внутри этой команды?

  • почему я должен использовать for [...] %%I в скриптах файлов, но for [...] %I в интерактивных сессий?

  • какие символы и в каком контексте? Как избежать знака процента? Например, как я могу Эхо %PROCESSOR_ARCHITECTURE% буквально? Я нашел это echo.exe %""PROCESSOR_ARCHITECTURE% работает, есть ли лучшее решение?

  • как сделать пар % матч? Образец:



    • set b=a,echo %a %b% c%%a a c%


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



  • как я могу гарантировать, что переменная переходит к команде в качестве одного аргумента, если когда-либо эта переменная содержит двойные кавычки?

  • как хранятся переменные, когда используя ? Например, если я делаю set a=a" b а то echo.%a% получить a" b. Однако, если я использую echo.exe от UnxUtils, я получаю a b. Как же так %a% расширяется по-другому?


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

852   6  

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

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

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

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

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

есть некоторые понятия, которые важны на протяжении всей этой фазы.

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

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

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

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

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

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

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

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

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

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

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

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

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

фаза 6) обработка вызовов / удвоение каретки: только если токен команды является вызовом, или если текст перед первым встречающимся стандартным разделителем токена является вызовом. Если вызов анализируется из большего маркера команды, то неиспользуемая часть добавляется к маркеру аргументов раньше процессуальное действие.

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

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

  • 7.1 - выполнить внутреннюю команду - если маркер команды заключен в кавычки, то пропустите этот шаг. В противном случае попытайтесь разобрать внутреннюю команду и выполнять.
    • следующие тесты выполняются для определения того, представляет ли маркер команды без кавычек внутреннюю команду:
      • если маркер команды точно соответствует внутренней команде, то выполните ее.
      • иначе сломать маркер команды до первого появления +/[]<space><tab>,; или =
        Если предыдущий текст является внутренней командой, то помните, что команда
        • если в режиме командной строки, или если команда из блока в скобках, если true или false командный блок, для командного блока DO, или участвует в конкатенации команд, то выполните внутреннюю команду
        • Else (должна быть автономная команда в пакетном режиме) сканируйте текущую папку и путь к файлу .COM, .ИСПОЛНЯЕМЫЙ. ,летучая мышь или. CMD-файл, базовое имя которого соответствует исходному маркеру команды
          • если первый соответствующий файл a .летучая мышь или. УМК, затем Гото 7.3.exec и выполнить этот скрипт
          • Else (матч не найден или первый матч есть .EXE или. COM) выполните запомнившуюся внутреннюю команду
      • иначе сломать маркер команды до первого появления .\ или :
        Если предыдущий текст не является внутренней командой, то goto 7.2
        В противном случае предыдущий текст может быть внутренней командой. Помнить об этом команда.
      • разбить маркер команды перед первым появлением +/[]<space><tab>,; или =
        Если предыдущий текст представляет собой путь к существующему файлу, то goto 7.2
        В противном случае выполните запомнившуюся внутреннюю команду.
    • если внутренняя команда анализируется из большего маркера команды, то неиспользуемая часть маркера команды включается в аргумент список
    • только потому, что маркер команды анализируется как внутренняя команда, это не означает, что она будет выполнена успешно. Каждая внутренняя команда имеет свои собственные правила относительно того, как анализируются Аргументы и параметры, и какой синтаксис разрешен.
    • все внутренние команды будут печатать справку вместо выполнения своей функции, если /? обнаружено. Большинство признают /? если он появляется в любом месте аргументов. Но несколько команд, таких как ECHO и SET, только помогают печатать, если первый маркер аргумента начинается с /?.
    • набор имеет интересную семантику:
      • если команда 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.
        • командный блок должен пройти весь процесс синтаксического анализа во второй раз, но на этот раз в контексте командной строки
        • ECHO начнет работать, а отложенное расширение обычно будет отключено (зависит от параметра реестра)
        • все изменения среды, внесенные блоком команд in clause, будут потеряны после того, как дочерний cmd.exe процесс завершается
      • для каждой итерации:
        • значения переменных определены
        • затем обрабатывается уже проанализированный командный блок DO, начиная с ФАЗЫ 4.
    • GOTO использует следующую логику для поиска :этикетка
      • метка анализируется из первого аргумента token
      • скрипт сканируется для следующего вхождения метки
        • сканирование начинается с текущей позиции файла
        • если достигнут конец файла, то сканирование возвращается к началу файла и продолжается до исходной начальной точки.
      • сканирование останавливается при первом появлении метки, которую он находит, и указатель файла устанавливается на строку, следующую непосредственно за меткой. Выполнение скрипта возобновляется с этого момента. Обратите внимание, что успешный true GOTO немедленно прервет любой проанализированный блок кода, в том числе для циклов.
      • если метка не найдена или маркер метки отсутствует, то происходит сбой GOTO, выводится сообщение об ошибке, и стек вызовов выскакивает. Это эффективно функционирует как EXIT/ B, за исключением любых уже проанализированных команд в текущем блоке команд, которые следуют GOTO все еще выполняются, но в контексте вызывающего объекта (контекст, который существует после выхода /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 не используется для определения тома для ::.
    • определите внешнюю команду выполнять.
      • это сложный процесс, который может включать текущий том, текущий каталог, переменную пути, переменную PATHEXT и или ассоциации файлов.
      • если правильная внешняя команда не может быть идентифицирована, то прервать с ошибкой.
    • если в режиме командной строки и маркер команды начинается с :, затем перейти 7.4
      Обратите внимание, что это редко, потому что предыдущий шаг будет прервана с ошибкой если только маркер команды не начинается с ::, и SUBST используется для определения объема для ::, и весь маркер команды является допустимым путем к внешней команде.
    • 7.3.старпома - выполнить внешнюю команду.
  • 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) Выполнить Команду

  • попытки вызвать или перейти 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 (Он же"оболочка"). Чаще всего разметка производится новообразованных процессов на C/C++ во время выполнения, но это не обязательно так, например, если новый процесс не был написан на C/C++, или если новый процесс проигнорирует argv и обработать ключи для себя (например,GetCommandLine()). На уровне ОС Windows проходит командной строки не разбитые на лексемы как одну строку к новым процессам. Это в отличие от большинства оболочек *nix, где оболочка маркирует аргументы последовательным, предсказуемым образом, прежде чем передавать их в вновь сформированный процесс. Все это означает, что вы можете столкнуться с дико расходящимся поведением токенизации аргументов в разных программах в Windows, поскольку отдельные программы часто берут токенизацию аргументов в свои руки.

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

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

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

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

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

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

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.


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

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

короче:

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

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

Comments

    Ничего не найдено.