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

StackOverflow https://stackoverflow.com/questions/4094699

Вопрос

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

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

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

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

Спасибо вам за ваш свет.

Это было полезно?

Решение

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

Анализатор пакетных строк:

Ниже приведен краткий обзор этапов, связанных с обработкой строки кода в пакетном файле:

Фаза 0) Строка чтения:

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

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

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

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

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

Фаза 5) Замедленное расширение: Только в том случае, если включено отложенное расширение

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

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

Этап 6) Обработка вызова / удвоение курсора: Только в том случае, если командный токен равен CALL

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


Вот подробная информация по каждому этапу:

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

Фаза 0) Строка чтения: Сначала прочтите строку ввода до конца <LF>.

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

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

  • Двойной %% заменяется одним %
  • Расширение аргументации (%*, %1, %2, и т.д.)
  • Расширение %var%, если var не существует , замените его ничем
  • Сначала строка усекается <LF> не внутри %var% расширение
  • Для получения полного объяснения прочтите первую половину этого из dbenham Тот же поток:Процентная Фаза

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

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

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

  • На этом этапе фаза 1 пакетной обработки уже преобразует переменную типа %%X в %X.В командной строке установлены другие правила процентного расширения для фазы 1.Именно по этой причине командные строки используют %X но пакетные файлы используют %%X для Переменных FOR.
  • Для имен переменных чувствителен к регистру, но ~modifiers не чувствительны к регистру.
  • ~modifiers имеют приоритет над именами переменных.Если символ , следующий за ~ является одновременно модификатором и допустимым ДЛЯ имени переменной, и существует последующий символ, который является активным ДЛЯ имени переменной, тогда этот символ интерпретируется как модификатор.
  • Имена переменных FOR являются глобальными, но только в контексте предложения DO.Если подпрограмма вызывается из предложения FOR DO, то переменные FOR не расширяются внутри вызываемой подпрограммы.Но если у процедуры есть своя собственная команда FOR, то ВСЕ в настоящее время определенные ДЛЯ переменных доступны внутренним командам DO.
  • Имена переменных FOR могут быть повторно использованы внутри вложенных FORS.Внутреннее значение FOR имеет приоритет, но как только ВНУТРЕННЕЕ значение FOR закрывается, восстанавливается внешнее значение FOR.
  • Если ECHO было включено в начале этой фазы, то фаза 3) повторяется для отображения проанализированных команд DO после расширения переменных FOR.

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

Фаза 5) Замедленное расширение: Только если включено отложенное расширение, команда не находится в блок в круглых скобках по обе стороны трубы, и команда не является пакетный скрипт "голый" (имя скрипта без круглых скобок, ВЫЗОВА, конкатенации команд или канала).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

Другие советы

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

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

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

"Пакетный язык" Microsoft (.bat) не является исключением в этой анархической среде, и она разработала свои собственные уникальные правила токенизации и экранирования.Также похоже, что командная строка cmd.exe выполняет некоторую предварительную обработку аргумента командной строки (в основном для замены переменных и экранирования) перед передачей аргумента вновь выполняемому процессу.Вы можете прочитать больше о низкоуровневых деталях пакетного языка и экранировании cmd в превосходных ответах Джеба и 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 с пакетом обновления 3.Скомпилирован с помощью 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) Процент расширенияНачиная с левого, сканируйте каждый символ для % или <LF>. Отказ Если найдено тогда

  • 1.05 (урезанная линия на <LF>)
    • Если персонаж <LF> потом
      • Падение (игнорировать) остаток линии от <LF> далее
      • Goto Phase 1.5 (полоса <CR>)
    • Еще характер должен быть %, поэтому перейти к 1,1
  • 1.1 (побег %) пропущена, если в режиме командной строки
    • Если пакетный режим и после чего следует другой % потом
      Заменять %% с одним % и продолжить сканирование
  • 1.2 (развернуть аргумент) пропущена, если в режиме командной строки
    • Еще, если пакетный режим тогда
      • Если последование * и расширения команд включены тогда
        Заменять %* С текстом всех аргументов командной строки (замените ни с тем, если нет аргументов) и продолжайте сканирование.
      • Остальное, если последование <digit> потом
        Заменять %<digit> с значением аргумента (замените ничем, если не определено) и продолжайте сканирование.
      • Остальное, если последование ~ и расширения команд включены тогда
        • Если после соблюдения дополнительного списка модификаторов аргументов, а затем требуется необходимый <digit> потом
          Заменять %~[modifiers]<digit> С модифицированным значением аргумента (замените ничем, если не определено или если указано $ PATH: MODIFIER не определен) и продолжить сканирование.
          Примечание. Модификаторы нечувствительны к регистру и могут появиться несколько раз в любом порядке, кроме $ PATH: MODIFIER может отображаться только один раз и должен быть последним модификатором до <digit>
        • Еще недействительный модифицированный синтаксис аргументов поднимает Фатальная ошибка: все анализируемые команды прерваны, а пакетная обработка прерывается, если в пакетном режиме!
  • 1.3 (расширить переменную)
    • Остальное, если расширения команд отключены, то
      Посмотрите на следующую строку персонажей, ломаю раньше % или конец буфера и вызовите их 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 (Escape Caret) Нужен для ! или ^ литералы
    • Если персонаж - карет ^ потом
      • Удалить ^
      • Сканируйте следующий символ и сохраните его как буквальный
      • Продолжить сканирование
  • 5.2 (расширить переменную)
    • Если персонаж есть !, потом
      • Если расширения команд отключены, то
        Посмотрите на следующую строку персонажей, ломаю раньше ! или <LF>, и позвоните им VAR (может быть пустой список)
        • Если следующий символ ! потом
          • Если var определен, то
            Заменять !VAR! со значением var и продолжить сканирование
          • Еще, если пакетный режим тогда
            Удалять !VAR! и продолжить сканирование
          • Иначе Goto 5.2.1
        • Иначе Goto 5.2.1
      • Иначе, если расширения команд включены, то
        Посмотрите на следующую строку персонажей, ломаю раньше !, :, или <LF>, и позвоните им VAR (может быть пустой список). Если var перерывается раньше : и последующий характер ! Затем включите : как последний символ в var и перерываться перед !
        • Если следующий символ ! потом
          • Если var существует, то
            Заменять !VAR! со значением var и продолжить сканирование
          • Еще, если пакетный режим тогда
            Удалять !VAR! и продолжить сканирование
          • Иначе Goto 5.2.1
        • Остальное, если следующий символ : потом
          • Если var не определен
            • Если пакетный режим тогда
              Удалять !VAR: и продолжить сканирование
            • Иначе Goto 5.2.1
          • Остальное, если следующая строка символов соответствует шаблону
            ~[integer][,[integer]]! потом
            Заменять !VAR:~[integer][,[integer]]! с подстрокой значения var (возможно, приводя к пустой строке) и продолжить сканирование
          • Остальное, если следующая строка символов соответствует шаблону [*]search=[replace]!, где поиск может включать любой набор символов, кроме =, и заменить может включать в себя любой набор символов, кроме !, потом
            Заменять !VAR:[*]search=[replace]! со значением var после выполнения поиска и замените (возможно, приводя к пустой строке) и продолжить сканирование
          • Иначе Goto 5.2.1
        • Иначе Goto 5.2.1
      • 5.2.1
        • Если пакетный режим, затем удалите !
          Остальное сохранить !
        • Продолжайте сканирование, начиная с следующего символа после !

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

Что касается системы .bat Файлы Go, вот этот тест:

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]

Последовательные двойные цитаты заставляют их потерять любые особые анализа способности, которые они могли иметь. @ Пример Бениота:

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

Quiz: Как вы проходите значение любой среды VAR в качестве Один аргумент (т.е. как %1) в файл летучей мыши?

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

Sane Sarsing кажется вечно разбитым.

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

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

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

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

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

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

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


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

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

Короче:

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top