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

145

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

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

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

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

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

Бенуа
источник
У Роба ван дер Вуде есть замечательная справка по пакетным сценариям и командной строке Windows на своем сайте.
JBRWilkinson

Ответы:

204

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

Пакетный анализатор строк:

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

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

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

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

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

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

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

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

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

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

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


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

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

Фаза 0) Прочитать строку: сначала прочитать строку ввода <LF>.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Фаза 3) Эхо проанализированных команд

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

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

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

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

dbenham
источник
4
Привет, jeb, спасибо за понимание… Это может быть трудно понять, но я постараюсь обдумать это! Кажется, вы провели много тестов! Спасибо за перевод ( administrator.de/… )
Бенуа
2
Пакетная фаза 5) - %% a уже будет изменен на% a на этапе 1, поэтому расширение цикла for действительно расширяет% a. Кроме того, я добавил более подробное объяснение фазы 1 пакетной обработки в ответ ниже (у меня нет прав на редактирование)
dbenham 01
3
Джеб - возможно, фазу 0 можно было бы перенести и совместить с фазой 6? Для меня это имеет больше смысла, или есть причина, по которой они так разделены?
dbenham
1
@aschipfl - Я обновил этот раздел. )Действительно функция почти как REMкоманда , когда счетчик скобки 0. Попробуйте оба из них из командной строки: ) Ignore thisиecho OK & ) Ignore this
dbenham
1
@aschipfl да, это правильно, поэтому иногда вы видите 'set "var =% expr%"! 'последний восклицательный знак будет удален, но форсирует фазу 5
jeb
62

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

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

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

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


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

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

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

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

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

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

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

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

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

> test.exe a \"b\" c
argv[1][a]
argv[2]["b"]
argv[3][c]
Майк Кларк
источник
Спасибо за ваш ответ. Меня еще больше озадачивает то, что TinyPerl не выводит то, что выводит ваша программа, и мне трудно понять, как это [a "b" c]могло бы стать [a "b] [c]постобработкой.
Бенуа
Теперь, когда я думаю об этом, эта разметка командной строки, вероятно, полностью выполняется средой выполнения C. Исполняемый файл может быть написан таким образом, чтобы он даже не использовал среду выполнения C, и в этом случае я думаю, что ему придется иметь дело с командной строкой дословно и нести ответственность за выполнение собственной токенизации (если она этого захочет). Или даже если ваше приложение использует среду выполнения C, вы можете игнорировать argc и argv и просто получить необработанную командную строку, например, через Win32 GetCommandLine. Возможно, TinyPerl игнорирует argv и просто размечает необработанную командную строку по своим собственным правилам.
Майк Кларк
4
«Помните, что с точки зрения Win32, командная строка - это просто строка, которая копируется в адресное пространство нового процесса. То, как запускающий процесс и новый процесс интерпретируют эту строку, определяется не правилами, а соглашением». -Рэймонд Чен blogs.msdn.com/b/oldnewthing/archive/2009/11/25/9928372.aspx
Майк Кларк
2
Спасибо за действительно хороший ответ. На мой взгляд, это многое объясняет. И это также объясняет, почему я иногда нахожу действительно ужасным работать с Windows…
Бенуа
Я обнаружил это в отношении обратных косых черт и кавычек во время преобразования из командной строки в argv для программ Win32 C ++. Счетчик обратной косой черты делится только на два, если за последней обратной косой чертой следует dblquote, а dblquote завершает строку, если перед ней четное количество обратных косых черт.
Бенуа
48

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

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

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

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

Приведенное выше помогает объяснить, почему эта партия

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

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

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

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

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

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

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

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

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

Фаза 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-символ) Требуется для литералов !или^
    • Если символ каретки, ^то
      • Удалить ^
      • Отсканируйте следующий символ и сохраните его как буквальный
      • Продолжить сканирование
  • 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!и продолжить сканирование
          • Else goto 5.2.1
        • Иначе , если следующий символ , :то
          • Если VAR не определен, то
            • В пакетном режиме нажмите
              Удалить !VAR:и продолжить сканирование.
            • Else goto 5.2.1
          • Иначе , если следующий символ , ~то
            • Если следующая строка символов совпадает с шаблоном, [integer][,[integer]]!то замените !VAR:~[integer][,[integer]]!на подстроку значения VAR (что может привести к пустой строке) и продолжить сканирование.
            • Else goto 5.2.1
          • Иначе, если следующая строка символов совпадает с шаблоном [*]search=[replace]!, где поиск может включать любой набор символов, кроме =, а замена может включать любой набор символов, кроме !, то
            заменить !VAR:[*]search=[replace]!на значение VAR после выполнения поиска и замены (возможно, в результате будет пустая строка) и продолжить сканирование
          • Else goto 5.2.1
        • Else goto 5.2.1
      • 5.2.1
        • Если в пакетном режиме удалить ведущий, !
          иначе сохранить ведущий!
        • Продолжить сканирование, начиная со следующего символа после сохраненного интерлиньяжа. !
dbenham
источник
3
+1, здесь отсутствует только синтаксис двоеточия и правила для %definedVar:a=b%vs %undefinedVar:a=b%и %var:~0x17,-010%форм
jeb
2
Хороший замечание - я расширил раздел расширения переменных, чтобы решить ваши проблемы. Я также расширил раздел расширения аргументов, чтобы заполнить некоторые недостающие детали.
dbenham
2
Получив дополнительную личную обратную связь от jeb, я добавил правило для имен переменных, заканчивающихся двоеточием, и добавил примечание 2. Я также добавил примечание 3 просто потому, что считал его интересным и важным.
dbenham 03
1
@aschipfl - Да, я хотел рассказать об этом подробнее, но не хотел спускаться в кроличью нору. Я намеренно отказался от обязательств, когда использовал термин [целое число]. В Правилах есть дополнительная информация о том, как CMD.EXE анализирует числа .
dbenham
1
Мне не хватает правил расширения для контекста cmd, например, что нет зарезервированных символов для первого символа имени переменной, например %<digit>, %*или %~. И поведение меняется для неопределенных переменных. Возможно, вам нужно открыть второй ответ
jeb
8

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

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

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

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

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

бобого
источник
Чтобы передать% t% в качестве единственного аргумента, вы можете использовать "% t:" = \ "%", то есть использовать синтаксис% VAR: str = replacement% для расширения переменной. Метасимволы оболочки, такие как | и & в переменной содержимое все еще может быть раскрыто и испортить оболочку, если вы не
избежите
@Toughy Итак, в моем примере tэто a "b c. У вас есть рецепт для получения этих 6 символов ( a, 2 × пространство ", bи c) , чтобы выглядеть как %1внутри .cmd? Хотя мне нравится твое мышление. args "%t:"=""%"довольно близко :-)
bobbogo
5

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

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

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

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

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

SS64
источник
0

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


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

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

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

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

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

Бенуа
источник
токенизатор и подстановка строк зависят от команды! «Набор» работает иначе, чем «колл» или даже «если»
jeb
да, а как насчет внешних команд? Полагаю, cmd.exe всегда передает им одни и те же аргументы?
Бенуа
1
cmd.exe всегда передает результат раскрытия в виде строки, а не токенов, внешней команде. Это зависит от внешней команды, как ее избежать и токенизировать, findstr использует обратную косую черту, следующая может использовать что-то еще
jeb
0

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

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

user7427029
источник