Пакетный скрипт: \ MyProg был неожиданным в это время - PullRequest
2 голосов
/ 14 февраля 2020

У меня есть простой пакетный скрипт test.bat, который перебирает аргументы командной строки:

@echo off
set prefix=C:\Program Files\MyProg
for %%x in (%*) do (
    echo %prefix%
)

эта версия скрипта работает без проблем в cmd.exe:

C:\>test a b
C:\Program Files\MyProg
C:\Program Files\MyProg

Однако, если я изменю пакетный файл на другое значение prefix, добавив (x86):

set prefix=C:\Program Files (x86)\MyProg

, сценарий завершится неудачей:

C:\>test a b
\MyProg was unexpected at this time.

какая катастрофа! Как я могу решить эту проблему?

Ответы [ 3 ]

5 голосов
/ 14 февраля 2020

Включение EnableDelayedExpansion с помощью !prefix! решает эту проблему:

@echo off
set prefix=C:\Program Files (x86)\MyProg
setlocal EnableDelayedExpansion
for %%x in (%*) do (
    echo !prefix!
)
endlocal

! Невероятно!

2 голосов
/ 14 февраля 2020

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

Windows Командный процессор анализирует весь блок кода, начиная с ( и заканчивая соответствием ) перед выполнением команды, использующей командный блок. Во время обработки командного блока все ссылки на переменные среды в форме %variable% заменяются текущим значением переменных среды или удаляются для переменной среды, которая вообще не существует. Таким образом, окончательно выполненный командный блок больше не содержит %variable%. Это поведение очень хорошо объяснено на Переменные работают не так, как ожидалось .

Так что код

set prefix=C:\Program Files (x86)\MyProg
for %%x in (%*) do (
    echo %prefix%
)

приводит к выполнению FOR с помощью

echo C:\Program Files (x86)\MyProg

Закрывающая скобка ) не находится внутри строки аргумента, заключенной в ". По этой причине командный процессор Windows интерпретирует его как конец командного блока команды FOR .

Синтаксически неправильно указывать еще одну команду в той же строке после ) интерпретируется как конец командного блока без использования оператора, такого как & или && или ||. По этой причине \MyProg интерпретируется как синтаксически недопустимая команда после ) маркировки конца командного блока.

Вывод справки при запуске cmd /? в окне командной строки в конце последней страницы объясняет, что имя файла, содержащее пробел или один из этих символов &()[]{}^=;!'+,`~ должно быть заключено в ", чтобы все эти символы интерпретировались буквально, за исключением !, если включено также замедленное расширение переменной среды.

использование " вокруг строки аргумента требуется не только для имен файлов, но и для любой строки аргумента, содержащей пробел или один из этих символов &()[]{}^=;!'+,`~ или операторов перенаправления <|>, которые также должны интерпретироваться буквально cmd.exe .

Таким образом, одним из решений будет:

set prefix=C:\Program Files (x86)\MyProg
for %%x in (%*) do (
    echo "%prefix%"
)

ECHO выводит префикс с обоими ", но рекомендуется выводить имена файлов / папок с ECHO всегда заключено в двойные кавычки.

Другим решением является экранирование ) с ^ в b Он интерпретируется как буквальный характер. В этом случае важно, чтобы строка с echo все еще имела ^) после замены %prefix% на строковое значение, присвоенное переменной среды prefix. По этой причине необходимо определить строку prefix с символом каретки, интерпретируемым как буквальный символ, что означает, что два ^ необходимо оставить равными ) при определении переменной среды.

set prefix=C:\Program Files (x86^^)\MyProg
for %%x in (%*) do (
    echo %prefix%
)

Переменная среды prefix теперь определяется строковым значением:

C:\Program Files (x86^)\MyProg

В результате командный блок FOR приводит к интерпретации ) как буквенного символа.

It Также возможно определить переменную окружения prefix, заключив ее в двойные кавычки, чтобы получить символ вставки ^, интерпретируемый как литеральный символ, что устраняет необходимость удваивать этот символ, иначе интерпретируемый как escape-символ.

set "prefix=C:\Program Files (x86^)\MyProg"
for %%x in (%*) do (
    echo %prefix%
)

Еще одно решение заключается в использовании отложенного расширения переменной среды:

@echo off
set prefix=C:\Program Files (x86)\MyProg
setlocal EnableDelayedExpansion
for %%x in (%*) do (
    echo !prefix!
)
endlocal

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

@echo off
set prefix=C:\Program Files (x86)\MyProg
setlocal EnableDelayedExpansion
for %%x in (%*) do (
    echo !prefix!\%%~x
)
endlocal

и выполнение командного файла w Три аргумента Hello! и Test и Start!. Восклицательные знаки больше не интерпретируются как буквенные символы из-за включенного расширения с задержкой. По этой причине вывод:

C:\Program Files (x86)\MyProg\Hello

Но вывод должен быть:

C:\Program Files (x86)\MyProg\Hello!
C:\Program Files (x86)\MyProg\Test
C:\Program Files (x86)\MyProg\Start!

Этот вывод можно получить с помощью:

set "prefix=C:\Program Files (x86^)\MyProg"
for %%x in (%*) do (
    echo %prefix%\%%~x
)

Однако на самом деле безопасным является печать объединенных строк, заключенных в двойные кавычки.

set "prefix=%ProgramFiles(x86)%\MyProg"
for %%I in (%*) do (
    echo "%prefix%\%%~I"
)

В этом случае вывод выводится для приведенного выше примера с тремя аргументами Hello! и Test и Start!:

"C:\Program Files (x86)\MyProg\Hello!"
"C:\Program Files (x86)\MyProg\Test"
"C:\Program Files (x86)\MyProg\Start!"

См. Также мой ответ по Почему нет строкового вывода с 'echo% var%' после использования 'set var = text' в командной строке? Это объясняет, почему переменная окружения prefix определяется с помощью используя " слева от имени переменной и в конце строкового значения, назначенного переменной среды. Это рекомендуемый синтаксис для определения переменной среды.

Кроме того, рекомендуется не использовать в качестве переменной l oop a чувствительную к регистру интерпретируемую букву, для которой учитывается регистр интерпретируемый модификатор, так как в противном случае результаты могут быть неожиданными, особенно при динамическом объединении переменных строк.

Пример:

@echo off
cls
echo Loop with %%~Xx:
for %%x in ("1" 2 3) do echo %%~Xx run.
echo Loop with %%~xX:
for %%X in ("1" 2 3) do echo %%~xX run.
echo Loop with %%~Ix:
for %%I in ("1" 2 3) do echo %%~Ix run.
echo Loop with %%~#x:
for %%# in ("1" 2 3) do echo %%~#x run.
pause

Вывод этого небольшого пакетного файла: :

Loop with %~Xx:
 run.
 run.
 run.
Loop with %~xX:
 run.
 run.
 run.
Loop with %~Ix:
1x run.
2x run.
3x run.
Loop with %~#x:
1x run.
2x run.
3x run.

Цель состоит в том, чтобы получить выходной номер с добавлением x и не ссылаться на несуществующее расширение файла трех строк в наборе команды FOR .

Поэтому буквы-модификаторы ADFNPSTXZadfnpstxz не следует использовать в качестве переменной l oop, хотя это возможно в большинстве случаев.

2 голосов
/ 14 февраля 2020

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

@echo off
set "prefix=C:\Program Files (x86)\MyProg"
for %%x in (%*) do echo %prefix%

Я также предполагаю, что вы также хотели использовать %%x таким образом, поэтому:

@echo off
set "prefix=C:\Program Files (x86)\MyProg"
for %%x in (%*) do echo %prefix%\%%~x
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...