Странная проблема с областью действия в файле .bat

Я пишу простой файл .bat и столкнулся с каким-то странным поведением. Есть пара мест, где я должен сделать простой if/else, но код внутри блоков, похоже, работает неправильно.

Вот простой случай, демонстрирующий ошибку:

@echo off

set MODE=FOOBAR

if "%~1"=="" (
  set MODE=all
  echo mode: %MODE%
) else (
  set MODE=%~1
  echo mode: %MODE%
)
echo mode: %MODE%

Результат, который я получаю:

C:\>test.bat test
mode: FOOBAR
mode: test

Почему эхо внутри блока кода не получает новое значение переменной? В самом коде, который я пишу, мне нужно создать несколько переменных и ссылаться на них в рамках if/else. Я мог бы переключить это, чтобы использовать метки и переходы вместо if/else, но это не кажется таким чистым.

Что вызывает такое поведение? Есть ли какое-то ограничение на переменные в блоках кода?


person Herms    schedule 20.11.2008    source источник


Ответы (3)


Вы столкнулись с проблемой расширения статической переменной cmd. Переменная MODE оценивается только один раз. Вы можете увидеть это, если опустите @echo off line.

Из множества /? документация:

Наконец, добавлена ​​поддержка отложенного раскрытия переменных среды. Эта поддержка всегда отключена по умолчанию, но ее можно включить/выключить с помощью переключателя командной строки /V в CMD.EXE. См. CMD/?

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

 set VAR=before
 if "%VAR%" == "before" (
     set VAR=after
     if "%VAR%" == "after" @echo If you see this, it worked
 )

никогда не отобразит сообщение, так как %VAR% в ОБОИХ операторах IF заменяется при чтении первого оператора IF, поскольку он логически включает в себя тело IF, которое является составным оператором. Таким образом, ЕСЛИ внутри составного оператора на самом деле сравнивает «до» с «после», которые никогда не будут равны. Точно так же следующий пример не будет работать должным образом:

set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%

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

for %i in (*) do set LIST= %i

который просто устанавливает LIST для последнего найденного файла.

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

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "!VAR!" == "after" @echo If you see this, it worked
)

set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
person user33675    schedule 20.11.2008
comment
Есть ли способ программно установить этот флаг в верхней части файла bat? В большинстве случаев это будет запускаться либо другим файлом bat, и среда, в которой он запускается, не фиксирована (может быть двойным щелчком, может запускаться из оболочки cygwin, может быть из cmd и т. д.). - person Herms; 20.11.2008
comment
@Herms, setlocal enabledelayedexpansion в начале, endlocal в конце. - person paxdiablo; 15.12.2008
comment
Спасибо. Это спасло мою задницу paxdiablo. - person Chris Benard; 15.10.2010

setlocal EnableDelayedExpansion

включит флаг /v

person Community    schedule 10.09.2009

Похоже, что чтение и запись используют разные правила области видимости.

Если убрать эту строку

set MODE=FOOBAR

он будет работать так, как ожидалось. Таким образом, вам, вероятно, понадобится сложная серия if/elses, чтобы получить переменные, заполненные так, как вы хотите.

person Harry Lime    schedule 20.11.2008
comment
На самом деле нет. Этого набора наверху изначально не было. Если я пропущу это, то при первом запуске bat-файла первое эхо будет пустым. Во второй раз, когда я запускаю его (из того же экземпляра cmd), первое эхо показывает последнее использованное значение. - person Herms; 20.11.2008
comment
Вы правы, я бы запустил скрипт во второй раз... хорошо замечено! - person Harry Lime; 20.11.2008