Як інтерпретатор команд Windows (CMD.EXE) розбирає сценарії?


142

Я наткнувся на ss64.com, який надає гарну допомогу щодо написання пакетних скриптів, які запускається інтерпретатор команд Windows.

Однак мені не вдалося знайти хорошого пояснення граматики пакетних скриптів, як все розширюється чи не розширюється та як уникнути речей.

Ось зразкові запитання, які мені не вдалося вирішити:

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

Дякую за ваше світло.


Роб ван дер Вуд на своєму сайті має дивовижні сценарії Batch та командування Windows .
JBRWilkinson

Відповіді:


200

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

Аналіз пакетної лінії:

Ось короткий огляд фаз в аналізаторі рядків пакетного файлу:

Фаза 0) Рядок читання:

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

Фаза 2) Обробляйте спеціальні символи, токенізуйте та будуйте кешований блок команд: Це складний процес, на який впливають такі речі, як цитати, спеціальні символи, роздільники маркерів та втечі карети.

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

Фаза 4) ДЛЯ %Xзмінної розширення: Тільки якщо команда FOR активна і команди після DO обробляються.

Фаза 5) Затримка розширення: Тільки якщо ввімкнено затримку розширення

Фаза 5.3) Обробка труб: Тільки якщо команди знаходяться з обох боків труби

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

Фаза 6) Обробка CALL / подвоєння карети: Тільки якщо маркер команди CALL

Фаза 7) Виконати: команда виконується


Ось деталі для кожної фази:

Зауважимо, що описані нижче фази є лише моделлю того, як працює парний аналізатор. Фактичні внутрішні cmd.exe можуть не відображати ці фази. Але ця модель ефективна при прогнозуванні поведінки пакетних сценаріїв.

Фаза 0) Рядок читання: Прочитайте рядок введення через перший <LF>.

  • Під час читання рядка, який слід проаналізувати як команду, <Ctrl-Z>(0x1A) читається як <LF>(LineFeed 0x0A)
  • Коли GOTO або CALL читає рядки під час сканування на: label,, <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>, то він трактується як буквальний, тобто цей процес не є рекурсивним.
    • Unescaped <LF>не в дужках
      • <LF> знімається і аналіз поточного рядка припиняється.
      • Будь-які символи в буфері рядків просто ігноруються.
    • Немає <LF>в межах блоку FOR IN
      • <LF> перетворюється в <space>
      • Якщо в кінці буфера рядка, то наступний рядок зчитується і додається до поточного.
    • Немає <LF>в межах командного блоку
      • <LF>перетворюється в <LF><space>, і the <space>обробляється як частина наступного рядка командного блоку.
      • Якщо в кінці буфера рядка, наступний рядок читається і додається до пробілу.
  • Якщо один із спеціальних символів & | <або >, розділіть рядок у цій точці для того, щоб обробляти труби, конкатенацію команд та перенаправлення.
    • У випадку з трубою ( |) кожна сторона є окремою командою (або командним блоком), яка отримує спеціальну обробку на фазі 5.3
    • У разі &, &&чи ||команда конкатенації, кожна сторона конкатенації розглядаються в якості окремої команди.
    • У разі <, <<, >або >>перенаправлення, положення перенаправлення аналізується, тимчасово вилучено, а потім додається до кінця поточної команди. Застереження про переадресацію складається з необов'язкової цифри обробки файлу, оператора перенаправлення та маркера призначення перенаправлення.
      • Якщо маркер, що передує оператору переадресації, є однією незміщеною цифрою, то цифра визначає ручку файлу, яку слід перенаправити. Якщо маркер ручки не знайдено, то виведіть значення переспрямування за замовчуванням на 1 (stdout), а вхідні параметри перенаправлення - на 0 (stdin).
  • Якщо найперший жетон для цієї команди (до переміщення перенаправлення до кінця) починається з @, це @має особливе значення. ( @не є особливим у будь-якому іншому контексті)
    • Спеціальний @знімається.
    • Якщо ECHO увімкнено, то ця команда разом з будь-якими наступними об'єднаними командами у цьому рядку виключаються із луни фази 3. Якщо @до початку відкриття (, то весь дужок блоку виключається з луни фази 3.
  • Круглі дужки процесу (передбачає складені оператори у кількох рядках):
    • Якщо аналізатор не шукає маркер команди, то (він не є спеціальним.
    • Якщо аналізатор шукає маркер команди і знаходить (, тоді запустіть нове складене оператор і збільште лічильник дужок
    • Якщо лічильник круглих дужок дорівнює> 0, то )припиняє складене твердження і зменшує лічильник дужок.
    • Якщо кінець рядка досягнуто і лічильник дужок дорівнює> 0, наступний рядок буде доданий до складеного оператора (починається знову з фази 0)
    • Якщо лічильник круглих дужок дорівнює 0, і аналізатор шукає команду, то )функціонує аналогічно REMоператору, доки за ним негайно слідує роздільник обмежень, спеціальний символ, новий рядок або кінець файлу.
      • Усі спеціальні символи втрачають значення, за винятком ^(можливе конкатенація рядків)
      • Після досягнення кінця логічного рядка вся "команда" відкидається.
  • Кожна команда розбирається на серію лексем. Перший маркер завжди трактується як командний маркер (після спеціального @зняття та перенаправлення переміщеного до кінця).
    • Провідні роздільники токенів перед командним маркером знімаються
    • Під час розбору токена команди, він (функціонує як роздільник обмежень маркера на додаток до стандартних роздільників токенів
    • Обробка наступних жетонів залежить від команди.
  • Більшість команд просто об'єднує всі аргументи після маркера команди в один маркер аргументу. Усі роздільники маркера аргументів збережені. Параметри аргументів зазвичай не розбираються до 7 фази.
  • Три команди отримують спеціальну обробку - IF, FOR і REM
    • ЯКЩО розділяється на дві або три чіткі частини, які обробляються незалежно. Помилка синтаксису в конструкції IF призведе до фатальної помилки синтаксису.
      • Операція порівняння - це фактична команда, яка проходить весь шлях до 7 фази
        • Усі параметри IF повністю проаналізовані у фазі 2.
        • Послідовні роздільники маркерів обвалюються в єдиний пробіл.
        • Залежно від оператора порівняння, буде ідентифіковано одне або два маркери значення.
      • Командний блок True - це набір команд після умови і аналізується, як і будь-який інший командний блок. Якщо використовується ELSE, то блок True повинен бути скоплений в скобках.
      • Необов'язковий блок команд False - це набір команд після ELSE. Знову ж, цей командний блок розбирається нормально.
      • Командні блоки True та False не автоматично перетікають у наступні фази. Подальша їх обробка контролюється 7 фазою.
    • FOR розбивається на дві частини після DO. Помилка синтаксису в конструкції FOR призведе до фатальної помилки синтаксису.
      • Частина через DO - це фактична команда ЗА ітерації FOR, яка проходить весь етап 7
        • Усі параметри FOR повністю проаналізовані у фазі 2.
        • Закладене в дужках пункт IN розглядає <LF>як <space>. Після розбору пункту IN всі лексеми об'єднуються разом, утворюючи єдиний маркер.
        • Послідовні незмінені / не котируються роздільники маркерів згортаються в єдиний простір протягом всієї команди FOR через DO.
      • Частина після DO - це командний блок, який нормально розбирається. Подальша обробка блоку команд DO контролюється ітерацією на етапі 7.
    • REM, виявлений у фазі 2, трактується різко, ніж усі інші команди.
      • Розбирається лише один маркер аргументу - аналізатор ігнорує символи після першого маркера аргументу.
      • Команда REM може з'являтися у виводі фази 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 активна і команди після DO обробляються.

  • На даний момент, фаза 1 пакетної обробки буде вже перетворена для змінних , як %%Xв %X. Командний рядок має різні відсоткові правила розширення для фази 1. Це причина використання командних рядків, %Xале пакетні файли використовують %%Xдля змінних FOR.
  • Для імен змінних чутливі регістри, але ~modifiersвони не відрізняються від регістру.
  • ~modifiersмати перевагу над іменами змінних. Якщо наступний символ ~є і модифікатором, і дійсним ім'ям змінної FOR, і існує наступний символ, який є активним іменем змінної FOR, тоді символ інтерпретується як модифікатор.
  • Імена змінних FOR глобальні, але лише в контексті пункту DO. Якщо рутина CALLED з пункту FOR DO, змінні FOR не розширюються в межах CALLED. Але якщо у рутини є своя команда FOR, то всі визначені в даний час змінні FOR доступні для внутрішніх команд DO.
  • Імена змінних FOR можуть бути повторно використані в межах вкладених FOR. Внутрішнє значення FOR має перевагу, але як тільки ВНУТРІШНЯ 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, де буде надруковано HELP for CALL.
  • Видаліть перше CALL, тож декілька CALL можна скласти
  • Подвійні всі грифи
  • Перезапустіть фази 1, 1.5 та 2, але не продовжуйте фазу 3
    • Будь-які подвійні піклування зводяться назад до однієї карети до тих пір, поки вони не цитуються. Але, на жаль, котируються роботи залишаються вдвічі.
    • Фаза 1 трохи змінюється
      • Помилки розширення на кроках 1.2 або 1.3 скасовують CALL, але помилка не є фатальною - пакетна обробка продовжується.
    • Завдання Фази 2 трохи змінені
      • Буде виявлено будь-яке нещодавно з'явилося некотируване, несказоване перенаправлення, яке не було виявлено у першому етапі 2 фази, але воно видаляється (включаючи ім'я файлу), фактично не виконуючи перенаправлення
      • Будь-яка щойно з’явилася без котирувальної, немальована каре в кінці рядка видаляється, не виконуючи продовження рядка
      • CALL припиняється без помилок, якщо виявлено що-небудь з наступного
        • Щойно з’являються без котирувань, без назви &або|
        • Отриманий маркер команди починається з без котирування, без націлювання (
        • Перший жетон після видаленого CALL розпочався з @
      • Якщо в результаті команди, здавалося б , діє IF або FOR, то виконання буде згодом зазнати невдач з помилкою про те , що IFабо FORне зізнається в якості внутрішньої чи зовнішньої команди.
      • Звичайно, CALL не припиняється в цьому 2-му раунді фази 2, якщо отриманий командний маркер є міткою, що починається з :.
  • Якщо результуючий маркер команди CALL, перезапустіть Фазу 6 (повторюється, поки не буде більше CALL)
  • Якщо результуючий командний маркер - це пакетний скрипт або мітка:, виконання CALL повністю обробляється залишком етапу 6.
    • Натисніть на поточну позицію файлу сценарію файлу на стеку викликів, щоб виконання моменту відновилося з правильної позиції, коли CALL завершено.
    • Установіть аргументи% 0,% 1,% 2, ...% N та% * для CALL, використовуючи всі результуючі лексеми
    • Якщо командний маркер - це мітка, яка починається з :, тоді
      • Фаза перезапуску 5. Це може вплинути на що: мітка CALLED. Але оскільки символи% 0 тощо вже налаштовані, це не змінить аргументів, переданих у процедуру CALLED.
      • Виконайте мітку GOTO, щоб розташувати вказівник на файл на початку підпрограми (ігноруйте будь-які інші лексеми, які можуть слідувати за: label) Дивіться Фазу 7 щодо правил роботи GOTO.
    • Інше управління передачею у вказаний пакетний сценарій.
    • Виконання мітки або сценарію CALLed продовжується до тих пір, поки не буде досягнуто EXIT / B або кінця файлу, після чого буде вискакуватися стек CALL і виконання відновиться зі збереженого положення файлу.
      Фаза 7 не виконується для CALLED-скриптів або: міток.
  • Крім того, результат 6 фази переходить у фазу 7 для виконання.

Фаза 7) Виконати: команда виконується

  • 7.1 - Виконати внутрішню команду - Якщо маркер команди цитується, пропустіть цей крок. В іншому випадку спробуйте розібрати внутрішню команду та виконати.
    • Наступні тести робляться, щоб визначити, чи маркер команди без котирування являє собою внутрішню команду:
      • Якщо маркер команди точно відповідає внутрішній команді, виконайте її.
      • Ще розбийте маркер команди перед першим появою + / [ ] <space> <tab> , ;або =
        Якщо попередній текст є внутрішньою командою, то запам'ятайте цю команду
        • Якщо в режимі командного рядка або якщо команда є з круглого блоку, якщо БЕЗПОЛЬЗОВИЙ або хибний блок команд, Блок команд ДЛО ДО або пов'язаний з об'єднанням команд, виконайте внутрішню команду
        • В іншому (має бути окрема команда в пакетному режимі) скануйте поточну папку та PATH на файл .COM, .EXE, .BAT або .CMD, базова назва якого відповідає оригінальному маркеру команди
          • Якщо першим відповідним файлом є .BAT або .CMD, тоді перейдіть до 7.3.exec та виконайте цей сценарій
          • В іншому (відповідність не знайдена або перша відповідність .EXE або .COM) виконується запам'ятована внутрішня команда
      • Ще розбийте маркер команди перед першим виникненням . \або :
        Якщо попередній текст не є внутрішньою командою, тоді перейдіть до 7.2
        Ще, попередній текст може бути внутрішньою командою. Запам'ятайте цю команду.
      • Розбийте маркер команди перед першим появою + / [ ] <space> <tab> , ;або =
        Якщо попередній текст є шляхом до існуючого файлу, тоді перейдіть до 7.2
        Інше виконайте запам'ятовувану внутрішню команду.
    • Якщо внутрішня команда аналізується з більшого маркера команди, то невикористана частина маркера команди включена до списку аргументів
    • Тільки тому, що маркер команди аналізується як внутрішня команда, не означає, що вона буде успішно виконана. Кожна внутрішня команда має свої правила щодо того, як розбираються аргументи та параметри, і який синтаксис дозволено.
    • Усі внутрішні команди надрукують довідку замість того, щоб виконувати свою функцію, якщо /?вона виявлена. Більшість визнає, /?якщо вона з’являється десь в аргументах. Але кілька команд, таких як ECHO та SET, допомагають друкувати лише у випадку, якщо починається перший маркер аргументу /?.
    • SET має цікаву семантику:
      • Якщо в команді SET є цитата перед тим, як ім'я змінної та розширення увімкнено
        set "name=content" ignored -> value =, content
        тоді текст між першим знаком рівності та останньою цитатою використовується як вміст (перша рівна та остання цитата виключена). Текст після останньої цитати ігнорується. Якщо після знака рівності немає лапки, то решта рядка використовується як зміст.
      • Якщо команда SET не має лапки перед назвою
        set name="content" not ignored -> value =, "content" not ignored
        тоді весь залишок рядка після рівного використовується як вміст, включаючи будь-які та всі лапки, які можуть бути присутніми.
    • Оцінюється порівняння IF, і залежно від того, чи умова істинна чи помилкова, обробляється відповідний вже проаналізований залежний командний блок, починаючи з фази 5.
    • Пункт IN команди переведено відповідним чином.
      • Якщо це FOR / F, який ітералізує вихід командного блоку, то:
        • Становище IN виконується в новому процесі cmd.exe через CMD / C.
        • Командний блок повинен пройти весь процес розбору вдруге, але цього разу в контексті командного рядка
        • ECHO почне ВКЛЮЧЕНО, і затримка розширення зазвичай починається відключена (залежно від налаштування реєстру)
        • Усі зміни середовища, внесені командним блоком пункту IN, будуть втрачені після завершення процесу дочірнього cmd.exe
      • Для кожної ітерації:
        • Визначені значення змінних FOR
        • Потім вже проаналізований блок команд DO обробляється, починаючи з фази 4.
    • GOTO використовує таку логіку, щоб знайти: label
      • Мітка аналізується з першого маркера аргументу
      • Сценарій сканується для наступного появи мітки
        • Сканування починається з поточного положення файлу
        • Якщо кінець файлу досягнуто, то сканування повертається до початку файлу і продовжується до початкової початкової точки.
      • Сканування припиняється при першому появі знайденої мітки, і вказівник файлу встановлюється на рядок, що знаходиться безпосередньо за міткою. Виконання сценарію поновлюється з цього моменту. Зауважте, що успішний справжній 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> , ;або =й випереджала залишок до аргументу маркерів (ів).
    • Якщо другий символ командного маркера є двокрапкою, то перевірте, чи може бути знайдений том, визначений 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 = вміст, то %%var%%розгортається до %content%.

Фаза 3) Відлуння проаналізованих команд

  • Це не виконується після фази 2. Вона виконується лише після фази 4 для блоку команд FOR DO.

Фаза 5) Затримка розширення: лише якщо ввімкнено DelayedExpansion

  • Якщо var не визначено, він !var!залишається незмінним.

Фаза 7) Виконати команду

  • Спроби CALL або GOTO a: label приводять до помилки.
  • Як уже зафіксовано на етапі 7, виконана мітка може призвести до помилки при різних сценаріях.
    • Пакетні виконані мітки можуть спричинити помилку лише тоді, коли вони починаються з ::
    • Мітки, виконані в командному рядку, майже завжди призводять до помилки

Розбір цілих значень

Існує багато різних контекстів, де cmd.exe аналізує цілі значення з рядків, а правила несумісні:

  • SET /A
  • IF
  • %var:~n,m% (змінне розширення підрядків)
  • FOR /F "TOKENS=n"
  • FOR /F "SKIP=n"
  • FOR /L %%A in (n1 n2 n3)
  • EXIT [/B] n

Детальну інформацію про ці правила можна знайти в Правилах того, як CMD.EXE аналізує номери


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

Сподіваюся, що це допомагає
Яну Еріку (jeb) - оригінальний автор та відкривач фаз
Дейв Бенхем (dbenham) - багато додаткового вмісту та редагування


4
Здрастуй, джеб, дякую за розуміння ... Це може бути важко зрозуміти, але я спробую все продумати! Ви, здається, зробили багато тестів! Дякую за переклад ( administrator.de/… )
Бенуа

2
Фаза партії 5) - %% a вже буде змінено на% a у фазі 1, тому розширення для циклу дійсно розширюється% a. Також у відповідь нижче (я не маю права редагування) я додав більш детальне пояснення пакетної фази 1
dbenham

3
Джеб - можливо, фазу 0 можна було б перемістити та поєднати з фазою 6? Це має більше сенсу для мене, чи є причина, чому вони поділяються так?
dbenham

1
@aschipfl - я оновив цей розділ. )Дійсно функція майже як REMкоманда , коли лічильник дужки 0. Спробуйте обидва з них з командного рядка: ) Ignore thisіecho OK & ) Ignore this
dbenham

1
@aschipfl так, це правильно, тому іноді ви бачите "встановити" var =% expr% "! 'останній знак оклику буде знято, але сила 5-го етапу
1616

62

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

Якщо це звучить як анархія, це так і є. Однак, так як велика кількість програм Windows , робить використовувати Microsoft C / C ++ середовища виконання argv, може бути в цілому корисно , щоб зрозуміти , як MSVCRT розмічає аргументи. Ось уривок:

  • Аргументи розмежовані пробілом, який є або пробілом, або вкладкою.
  • Рядок, оточений подвійними лапками, трактується як один аргумент, незалежно від пробілу, що міститься всередині. Цитований рядок може бути вбудований в аргумент. Зауважте, що карета (^) не розпізнається як символ втечі або роздільник.
  • Подвійний лапок, який передує зворотній косої риси, \ "інтерпретується як буквальний подвійний лапки (").
  • Підсумки трактуються буквально, якщо тільки вони не передують подвійній лапці.
  • Якщо за парною косою косою рисою супроводжується подвійний лапок, то по одному косому рисові () розміщується в масиві argv для кожної пари косих косих рядків (\), а подвійний лапки (") інтерпретується як роздільник рядків.
  • Якщо за непарною косою косою рисою супроводжується подвійною лапкою, то по одній косої риски () розміщується в масиві 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 командний рядок - це лише рядок, який копіюється в адресний простір нового процесу. Як процес запуску, так і новий процес інтерпретують цей рядок, регулюються не правилами, а конвенцією." -Raymond
Майк Кларк

2
Дякую за справді приємну відповідь. Це багато що пояснює на мою думку. І це також пояснює, чому іноді мені здається, що по-справжньому хитро працювати з Windows…
Бенуа

Я виявив це щодо зворотних нахилів та котирувань під час перетворення з командного рядка в argv, для програм Win32 C ++. Підрахунок зворотних косових ринків ділиться лише на два, коли після останньої косої риски слідує dblquote, а dblquote закінчує рядок, коли раніше є четне число зворотних косих рисів.
Бенуа

47

Правила розширення у відсотках

Ось розширене пояснення фази 1 у відповіді jeb (Дійсно і для пакетного режиму, і для режиму командного рядка).

Фаза 1) Процентне розширення Починаючи зліва, скануйте кожен символ на %або <LF>. Якщо потім знайдеться

  • 1,05 (обрізана лінія у <LF>)
    • Якщо персонаж <LF>тоді
      • Відкиньте (ігноруйте) залишок рядка <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%і продовжте сканування
        • Інше goto 1.4
      • Інше goto 1.4
    • В іншому випадку, якщо розширення команд увімкнено, тоді
      перегляньте наступний рядок символів, перериваючись до % :або в кінці буфера, і назвіть їх VAR (можливо, це порожній список). Якщо VAR перерветься до, :а наступний символ %тоді буде включений :як останній символ у VAR і перерваться раніше %.
      • Якщо наступний символ - %то
        • Якщо значення VAR визначено,
          замініть %VAR%значення VAR і продовжте сканування
        • В іншому випадку пакетний режим, то
          видаліть %VAR%і продовжте сканування
        • Інше goto 1.4
      • Ще, якщо наступний символ - це :тоді
        • Якщо VAR не визначено, то
          • Якщо пакетний режим, тоді
            видаліть %VAR:і продовжте сканування.
          • Інше goto 1.4
        • Ще, якщо наступний символ - це ~тоді
          • Якщо наступний рядок символів відповідає шаблону, [integer][,[integer]]%тоді
            замініть %VAR:~[integer][,[integer]]%підрядком значення VAR (можливо, це призведе до порожнього рядка) і продовжте сканування.
          • Інше goto 1.4
        • В іншому випадку, якщо слідує =або *=після цього
          Недійсний синтаксис пошуку і заміни змінної викликає фатальну помилку: Усі проаналізовані команди перервані, а пакетна обробка перервана, якщо в пакетному режимі!
        • В іншому випадку, якщо наступний рядок символів відповідає шаблону [*]search=[replace]%, де пошук може включати будь-який набір символів, за винятком =, а заміна може включати будь-який набір символів за винятком %, а потім
          замінити %VAR:[*]search=[replace]%значенням VAR після виконання пошуку та заміни (можливо, це призведе до порожнього рядка) та продовжити сканування
        • Інше 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 - Ще одне цікаве наслідок правил розбору%: Змінні, що містять: у імені можна визначити, але їх неможливо розширити, якщо розширення команд не вимкнено. Є один виняток - ім’я змінної, що містить в кінці одну двокрапку, може бути розширено, коли ввімкнено розширення команд. Однак ви не можете виконувати підрядки або пошук та заміну операцій над іменами змінних, що закінчуються двокрапкою. Наведений нижче пакетний файл (люб’язно наданий) демонструє цю поведінку

@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 фази у відповіді jeb (Дійсно і для пакетного режиму, і для режиму командного рядка)

Фаза 5) Затримка розширення

Ця фаза пропускається, якщо застосовується будь-яке з наступних умов:

  • Затримка розширення вимкнена.
  • Команда знаходиться в блокувальному блоці з обох боків труби.
  • Маркер входить команди є «голим» пакетним сценарієм, тобто він не пов'язаний з CALL, в дужках блоку, будь-яку форму команди конкатенації ( &, &&або ||), або трубу |.

Процес затримки розширення застосовується до токенів самостійно. Команда може мати кілька маркерів:

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

Зміни в жетонах, які не містять, не вносяться !.

Для кожного маркера , який дійсно містить щонайменше один !, сканувати кожен символ зліва направо на ^або !, і якщо знайдений, то

  • 5.1 (втеча від карети) Потрібна !або для ^літералістів
    • Якщо символ є каретка , ^то
      • Видаліть ^
      • Скануйте наступний символ і збережіть його як буквальний
      • Продовжте сканування
  • 5.2 (розширити змінну)
    • Якщо характер є !, значить
      • Якщо розширення команд вимкнено,
        перегляньте наступний рядок символів, перериваючись до !або <LF>, і назвіть їх VAR (можливо, це порожній список)
        • Якщо наступний символ - !то
          • Якщо визначено значення VAR ,
            замініть !VAR!значення VAR та продовжте сканування
          • В іншому випадку пакетний режим, то
            видаліть !VAR!і продовжте сканування
          • Інше goto 5.2.1
        • Інше goto 5.2.1
      • Інакше , якщо розширення команди включені потім
        Подивіться на наступний рядок символів, розбивши перед тим !, :чи <LF>, і називати їх УАК (може бути порожньою список). Якщо VAR перерветься до, :а наступний символ !тоді буде включений :як останній символ у VAR і перерваться раніше!
        • Якщо наступний символ - !то
          • Якщо VAR існує,
            замініть !VAR!значення VAR і продовжте сканування
          • В іншому випадку пакетний режим, то
            видаліть !VAR!і продовжте сканування
          • Інше goto 5.2.1
        • Ще, якщо наступний символ - це :тоді
          • Якщо VAR не визначено, то
            • Якщо пакетний режим, тоді
              видаліть !VAR:і продовжте сканування
            • Інше goto 5.2.1
          • Ще, якщо наступний символ - це ~тоді
            • Якщо наступний рядок символів відповідає шаблону, [integer][,[integer]]!тоді замініть !VAR:~[integer][,[integer]]!підрядком значення VAR (можливо, це призведе до порожнього рядка) і продовжте сканування.
            • Інше goto 5.2.1
          • В іншому випадку, якщо наступний рядок символів відповідає шаблону [*]search=[replace]!, де пошук може включати будь-який набір символів, за винятком =, а заміна може включати будь-який набір символів за винятком !, а потім
            замінити !VAR:[*]search=[replace]!значенням VAR після виконання пошуку та заміни (можливо, це призведе до порожнього рядка) та продовжити сканування
          • Інше goto 5.2.1
        • Інше goto 5.2.1
      • 5.2.1
        • Якщо пакетний режим, тоді видаліть провідний !
          Else, збережіть ведучий!
        • Продовжуйте сканування, починаючи з наступного символу після збереженого ведучого !

3
+1, тут відсутні лише синтаксис та правила двокрапки для %definedVar:a=b%vs %undefinedVar:a=b%та %var:~0x17,-010%форм
jeb

2
Хороший момент - я розширив розділ змінної розширення, щоб вирішити ваші проблеми. Я також розширив розділ розширення аргументів, щоб заповнити деякі відсутні деталі.
dbenham

2
Отримавши додаткові приватні відгуки від jeb, я додав правило для імен змінних, що закінчуються двокрапкою, і додав примітку 2. Я також додав примітку 3 просто тому, що вважав це цікавим і важливим.
dbenham

1
@aschipfl - Так, я розглядав детальніше про це, але не хотів спускатися з цієї кролячої нори. Коли я використовував термін [ціле число], я навмисно не приймав до уваги. В Правилах є додаткова інформація про те, як CMD.EXE аналізує номери .
dbenham

1
Мені не вистачає правил розширення для cmd контексту, як, наприклад, що для першого символу імені змінної типу %<digit>, %*або , немає зарезервованих символів %~. І поведінка змінюється для невизначених змінних. Можливо, вам потрібно відкрити другу відповідь
jeb

7

Як вказувалося, команди передають весь рядок аргументів у μSoft land, і саме вони розбирають це на окремі аргументи для власного використання. Між різними програмами в цьому немає послідовності, і тому немає жодного набору правил, який описував би цей процес. Вам дійсно потрібно перевірити кожен кутовий регістр на предмет будь-якої бібліотеки С, якою використовується ваша програма.

Що стосується системних .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]

Послідовні подвійні лапки змушують їх втрачати будь-які особливі здібності до розбору, які вони, можливо, мали. @ Приклад Беніота:

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

Тест: Як ви передаєте значення будь-якого var середовища в якості одного аргументу (тобто як %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 = заміна% для змінного розширення. Метахарактеристики оболонки типу | і & в змістовному вмісті все ще можна виявити і зіпсувати шкаралупу, окрім випадків, коли ви знову не
уникнете

@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%

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


0

редагувати: див. прийняту відповідь, наступне - неправильне, і пояснює лише те, як передати командний рядок TinyPerl.


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

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

Коротко:

"a """ b "" c"""складається з двох рядків: a " b "іc"

"a"", "a"""і "a""""всі вони однакові, якщо в кінці рядка


токенізатор і стрибування струн залежить від команди! А «набір» працює відрізняється то «виклик» або навіть «якщо»
Джеб

так, але як бути із зовнішніми командами? Я думаю, cmd.exe завжди передає їм однакові аргументи?
Бенуа

1
cmd.exe завжди передає результат розширення у вигляді рядка, а не лексеми до зовнішньої команди. Залежно від зовнішньої команди, як уникнути та токенізувати її, findstr використовує зворотний нахил, наступний може використовувати щось інше
jeb

0

Зауважте, що Microsoft опублікувала вихідний код свого терміналу. Він може працювати аналогічно командному рядку щодо синтаксичного розбору. Можливо, комусь цікаво тестувати правила розбору інженерного розбору на відповідність правилам розбору терміналу.

Посилання на вихідний код.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.