Почему отложенное расширение в пакетном файле не работает в этом случае?

Этот код

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

Я понятия не имею, является ли это документированным поведением относительно того, как должно работать стандартное и замедленное расширение или просто деталь реализации (что означает, что он может сломаться в будущем)