Почему отложенное расширение в пакетном файле не работает в этом случае?
Этот код
@echo off
setlocal EnableDelayedExpansion
set myvar=first
set first=second
echo myvar:!myvar!
set myvar=!myvar!
echo myvar:!myvar!
дает
myvar:first
myvar:first
в Windows Vista с пакетом обновления 2 (SP2).
Результат, который я ожидал,
myvar:first
myvar:second
Почему разница и как получить желаемый эффект?
Ответы
Ответ 1
Проблема заключается в том, что set myvar=!myvar!
расширяется до set myvar=first
,
вы установите его с тем же содержимым, а затем попросите echo myvar:!myvar!
показать содержимое myvar.
Я попытаюсь добавить еще несколько объяснений, даже если Aacini и shf301 уже ответили на вопрос.
Оба показали двойное расширение с конструкцией !%var%!
, и Аачини объяснил, почему он может работать, и почему обратная версия %!var!%
не может работать.
ИМХО существует четыре разных разложения.
Отложенное расширение:
Как объяснил Аачини, отсроченное расширение защищено от любых специальных символов в содержимом (он может обрабатывать ВСЕ символы от 0x01 до 0xFF).
Процент расширения:
Процентное расширение не может обрабатывать или удалять некоторые символы (даже с экранированием).
Он может быть полезен для простого контента, поскольку он может расширять переменные после барьера endlocal
.
setlocal
set "myVar=simple content"
(
endlocal
set result=%myVar%
)
Расширение FOR-Loop-Parameters:
Это безопасно, если отложенное расширение отключено, иначе фаза замедленного расширения выполняется после расширения переменных %% a.
Это может быть полезно, поскольку оно может расширять переменные после барьера endlocal
setlocal EnableDelayedExpansion
set "var=complex content &<>!"
for /F "delims=" %%A in ("!var!") DO (
endlocal
set "result=%%A"
)
Расширение SET:
set var
расширяет также переменную, и она всегда безопасна и работает независимо от режима с задержкой расширения.
Аачини просто объяснил, как работает конструкция call %%%var%%%
, я хочу только дать некоторые дополнительные замечания.
call
является штабелируемым, вы можете использовать многие из них, и каждый перезапускает парсер.
set "var=%%var%%#"
call call call call call echo %var%
результат %var%######
Но call
имеет много недостатков/побочных эффектов!
Каждый вызов удваивает все кадры ^
Вы можете сказать: "Эй, я проверил это, и я не вижу никакого удвоения"
call call call call echo ^^
результат ^
Тем не менее, это правда, но оно в основном скрыто, поскольку каждый перезапуск также имеет специальную фазу символов, где каретки ускользают от следующего символа, но вы можете видеть эффект удвоения с помощью
call call call call echo "^^"^^
результат "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"^
Даже если расширение вызова перезапускает синтаксический анализатор, вы никогда не сможете использовать замедленное расширение в любой фазе (только в первом).
call
перестает работать, если обнаруживает неоткрытые специальные символы.
echo you ^& me
call echo you & me
call echo you ^& me
call echo you ^^& me
call echo you ^^^& me
Только первые результаты вывода you & me
, все остальные терпят неудачу.
Другая проблема заключается в том, что вызов экстремально медленный, a call set var=content
~ 50 раз медленнее, чем set var=content
, причина в том, что вызов пытается запустить внешнюю программу.
@echo off
setlocal
(
echo echo *** External batch, parameters are '%%*'
) > set.bat
set "var="
call set var=hello
set var
Надеюсь, это было интересно немного...
И если вы хотите углубиться, вы можете прочитать ЗВОНИТЕ, или лучше избегать вызова
и Как интерпретатор сценариев Windows Command Interpreter (CMD.EXE)?
Ответ 2
Эта проблема не связана напрямую с Delayed variable Expansion, а с тем, что требуются два расширения значений: первая дает имя переменной, а вторая должна заменить это имя на его значение. Прямой способ сделать это через два расширения в той же строке, что и в предыдущем ответе: set myvar=!%myvar%!
работает, потому что расширение% var% выполняется до того, как анализируется командная строка для исполнения, тогда как! Var! расширение выполняется позже, непосредственно перед выполнением команды (отсюда и "отложенное" имя). Это означает, что расширение% var% может предоставлять части команды и может вызывать синтаксические ошибки, но! Var! не. Например, if %var%==value ...
вызывает ошибку, если var пуст или имеет пробелы, но if !var!==value ...
никогда не вызывает синтаксическую ошибку.
Двойное расширение значений может быть достигнуто другими способами, которые не связаны с задержкой переменной Expansion. Например, мы можем создать вспомогательный пакетный файл, который выполняет второе расширение:
echo myvar:%myvar%
echo set myvar=%%%myvar%%%> auxiliary.bat
call auxiliary
echo myvar:%myvar%
Предыдущий метод может использоваться для создания третьего или даже более глубокого расширения уровня и даже сочетаться с Delayed Expansions для создания очень сложных команд управления стоимостью. Этот вопрос не просто любопытство, но и ключ к доступу к элементам массива или связанным спискам. Например:
set month[1]=January
set month[2]=February
. . .
set month[12]=December
for /f "tokens=1-3 delims=/" %%a in ("%date%") do echo Today is !month[%%a]! %%b, %%c
Ответ 3
То, что вы пытаетесь сделать, не будет работать. Задержка расширения только изменяет поведение расширения переменной переменной внутри блока. Это не позволяет вам использовать псевдонимы/вложенность (из-за отсутствия лучшего слова), которые вы пытаетесь выполнить.
set myvar=first
устанавливает переменную myvar в текст "first". set first=second
сначала устанавливает переменную в текст "второй". Между этими двумя строками нет связи. myvar
никогда не будет оценивать то, что явно не установлено.
Я не верю, что в любом случае вы можете выполнить то, что вы пытаетесь сделать здесь.
* Изменить *
ОК, посмотрев ваш ответ. Я вижу, как это работает, вы можете получить желаемый результат:
@echo off
setlocal EnableDelayedExpansion
set myvar=first
set first=second
echo myvar:%myvar%
set myvar=!%myvar%!
echo myvar:%myvar%
Итак, магия, похоже, происходит из-за того, как происходит стандартное и замедленное расширение. Строка set myvar=!%myvar%!
, по-видимому, сначала расширяется стандартным расширителем до set myvar=!first!
(вы увидите это, если вы запустите script с помощью echo on
). Затем отложенный расширитель запускается и расширяет !first
до "секунды" и устанавливает для него myvar
.
Я понятия не имею, является ли это документированным поведением относительно того, как должно работать стандартное и замедленное расширение или просто деталь реализации (что означает, что он может сломаться в будущем)