Подтвердить что ты не робот

Вывести переменную среды ENDLOCAL

У меня есть командный файл, который вычисляет переменную через ряд промежуточных переменных:

@echo off
setlocal

set base=compute directory
set pkg=compute sub-directory
set scripts=%base%\%pkg%\Scripts

endlocal

%scripts%\activate.bat

script в последней строке не вызывается, потому что он приходит после endlocal, который сжимает переменную среды scripts, но она должна появиться после endlocal, поскольку она предназначена для установки группы других переменных среды для использования пользователем.

Как мне назначить script, кто хочет установить постоянные переменные среды, но какое местоположение определяется временной переменной среды?

Я знаю, что могу создать временный командный файл до endlocal и называть его после endlocal, что я буду делать, если ничего не выйдет, но я хотел бы знать, есть ли менее приемлемое решение для сдерживания.

4b9b3361

Ответ 1

Шаблон ENDLOCAL & SET VAR=%TEMPVAR% классический. Но бывают ситуации, когда они не идеальны.

Если вы не знаете содержимое TEMPVAR, вы можете столкнуться с проблемами, если значение содержит специальные символы, такие как < > & или |. Вы можете в целом защитить от этого, используя кавычки типа SET "VAR=%TEMPVAR%", но это может вызвать проблемы, если есть специальные символы, и значение уже указано.

A FOR выражение - отличный выбор для переноса значения через барьер ENDLOCAL, если вас беспокоят специальные символы. Отсроченное расширение должно быть включено до ENDLOCAL и отключено после ENDLOCAL.

setlocal enableDelayedExpansion
set "TEMPVAR=This & "that ^& the other thing"
for /f "delims=" %%A in (""!TEMPVAR!"") do endlocal & set "VAR=%%~A"

Ограничения:

  • Если после ENDLOCAL включено замедленное расширение, то окончательное значение будет искажено, если TEMPVAR содержит !.

  • значения, содержащие символ lineFeed, нельзя переносить

Если вы должны вернуть несколько значений и знаете символ, который не может отображаться ни в одном значении, просто используйте соответствующие опции FOR/F. Например, если я знаю, что значения не могут содержать |:

setlocal enableDelayedExpansion
set "temp1=val1"
set "temp2=val2"
for /f "tokens=1,2 delims=|" %%A in (""!temp1!"|"!temp2!"") do (
   endLocal
   set "var1=%%~A"
   set "var2=%%~B"
)

Если вы должны вернуть несколько значений, а набор символов неограничен, используйте вложенные петли FOR/F:

setlocal enableDelayedExpansion
set "temp1=val1"
set "temp2=val2"
for /f "delims=" %%A in (""!temp1!"") do (
  for /f "delims=" %%B in (""!temp2!"") do (
    endlocal
    set "var1=%%~A"
    set "var2=%%~B"
  )
)

Определенно проверьте jeb answer для безопасного, пуленепробиваемого метода, который работает для всех возможных значений во всех ситуациях.

2017-08-21 - Новая функция RETURN.BAT
Я работал с пользователем DosTips jeb, чтобы разработать пакетную утилиту RETURN.BAT, которая может быть использована для выхода из script или вызвана и возвращать одну или несколько переменных через барьер ENDLOCAL. Очень круто: -)

Ниже приведена версия 3.0. Я, скорее всего, не буду поддерживать этот код в актуальном состоянии. Лучше всего следовать ссылке, чтобы убедиться, что вы получите последнюю версию и посмотрите пример использования.

RETURN.BAT

::RETURN.BAT Version 3.0
@if "%~2" equ "" (goto :return.special) else goto :return
:::
:::call RETURN  ValueVar  ReturnVar  [ErrorCode]
:::  Used by batch functions to EXIT /B and safely return any value across the
:::  ENDLOCAL barrier.
:::    ValueVar  = The name of the local variable containing the return value.
:::    ReturnVar = The name of the variable to receive the return value.
:::    ErrorCode = The returned ERRORLEVEL, defaults to 0 if not specified.
:::
:::call RETURN "ValueVar1 ValueVar2 ..." "ReturnVar1 ReturnVar2 ..." [ErrorCode]
:::  Same as before, except the first and second arugments are quoted and space
:::  delimited lists of variable names.
:::
:::  Note that the total length of all assignments (variable names and values)
:::  must be less then 3.8k bytes. No checks are performed to verify that all
:::  assignments fit within the limit. Variable names must not contain space,
:::  tab, comma, semicolon, caret, asterisk, question mark, or exclamation point.
:::
:::call RETURN  init
:::  Defines return.LF and return.CR variables. Not required, but should be
:::  called once at the top of your script to improve performance of RETURN.
:::
:::return /?
:::  Displays this help
:::
:::return /V
:::  Displays the version of RETURN.BAT
:::
:::
:::RETURN.BAT was written by Dave Benham and DosTips user jeb, and was originally
:::posted within the folloing DosTips thread:
:::  http://www.dostips.com/forum/viewtopic.php?f=3&t=6496
:::
::==============================================================================
::  If the code below is copied within a script, then the :return.special code
::  can be removed, and your script can use the following calls:
::
::    call :return   ValueVar  ReturnVar  [ErrorCode]
::
::    call :return.init
::

:return  ValueVar  ReturnVar  [ErrorCode]
:: Safely returns any value(s) across the ENDLOCAL barrier. Default ErrorCode is 0
setlocal enableDelayedExpansion
if not defined return.LF call :return.init
if not defined return.CR call :return.init
set "return.normalCmd="
set "return.delayedCmd="
set "return.vars=%~2"
for %%a in (%~1) do for /f "tokens=1*" %%b in ("!return.vars!") do (
  set "return.normal=!%%a!"
  if defined return.normal (
    set "return.normal=!return.normal:%%=%%3!"
    set "return.normal=!return.normal:"=%%4!"
    for %%C in ("!return.LF!") do set "return.normal=!return.normal:%%~C=%%~1!"
    for %%C in ("!return.CR!") do set "return.normal=!return.normal:%%~C=%%2!"
    set "return.delayed=!return.normal:^=^^^^!"
  ) else set "return.delayed="
  if defined return.delayed call :return.setDelayed
  set "return.normalCmd=!return.normalCmd!&set "%%b=!return.normal!"^!"
  set "return.delayedCmd=!return.delayedCmd!&set "%%b=!return.delayed!"^!"
  set "return.vars=%%c"
)
set "err=%~3"
if not defined err set "err=0"
for %%1 in ("!return.LF!") do for /f "tokens=1-3" %%2 in (^"!return.CR! %% "") do (
  (goto) 2>nul
  (goto) 2>nul
  if "^!^" equ "^!" (%return.delayedCmd:~1%) else %return.normalCmd:~1%
  if %err% equ 0 (call ) else if %err% equ 1 (call) else cmd /c exit %err%
)

:return.setDelayed
set "return.delayed=%return.delayed:!=^^^!%" !
exit /b

:return.special
@if /i "%~1" equ "init" goto return.init
@if "%~1" equ "/?" (
  for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
  exit /b 0
)
@if /i "%~1" equ "/V" (
  for /f "tokens=* delims=:" %%A in ('findstr /rc:"^::RETURN.BAT Version" "%~f0"') do @echo %%A
  exit /b 0
)
@>&2 echo ERROR: Invalid call to RETURN.BAT
@exit /b 1


:return.init  -  Initializes the return.LF and return.CR variables
set ^"return.LF=^

^" The empty line above is critical - DO NOT REMOVE
for /f %%C in ('copy /z "%~f0" nul') do set "return.CR=%%C"
exit /b 0

Ответ 2

@ECHO OFF  
SETLOCAL  

REM Keep in mind that BAR in the next statement could be anything, including %1, etc.  
SET FOO=BAR  

ENDLOCAL && SET FOO=%FOO%

Ответ 3

ответа dbenham является хорошим решением для "нормальных" строк, но с восклицательными знаками ! он не работает, если после ENDLOCAL включено замедленное расширение (dbenham сказал это тоже).

Но он всегда будет терпеть неудачу с каким-то сложным контентом, таким как встроенные переводы строк,
поскольку FOR/F будет разделять контент на несколько строк.
Это приведет к странному поведению, endlocal будет выполняться несколько раз (для каждой строки), поэтому код не является доказательством пули.

Существуют пуленепробиваемые решения, но они немного беспорядочны:-)
версия макроса существует SO: Сохранение восклицания..., использовать это легко, но читать это...

Или вы можете использовать блок кода, вы можете вставить его в свои функции.
Dbenham и я разработали эту технику в потоке Re: новые функции:: chr,: asc,: asciiMap,
есть также объяснения этой техники

@echo off
setlocal EnableDelayedExpansion
cls
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
set LF=^


rem TWO Empty lines are neccessary
set "original=zero*? %%~A%%~B%%~C%%~L!LF!one&line!LF!two with exclam^! !LF!three with "quotes^&"&"!LF!four with ^^^^ ^| ^< ^> ( ) ^& ^^^! ^"!LF!xxxxxwith CR!CR!five !LF!six with ^"^"Q ^"^"L still six "

setlocal DisableDelayedExpansion
call :lfTest result original

setlocal EnableDelayedExpansion
echo The result with disabled delayed expansion is:
if !original! == !result! (echo OK) ELSE echo !result!

call :lfTest result original
echo The result with enabled delayed expansion is:
if !original! == !result! (echo OK) ELSE echo !result!
echo ------------------
echo !original!

goto :eof

::::::::::::::::::::
:lfTest
setlocal
set "NotDelayedFlag=!"
echo(
if defined NotDelayedFlag (echo lfTest was called with Delayed Expansion DISABLED) else echo lfTest was called with Delayed Expansion ENABLED
setlocal EnableDelayedExpansion
set "var=!%~2!"

rem echo the input is:
rem echo !var!
echo(

rem ** Prepare for return
set "var=!var:%%=%%~1!"
set "var=!var:"=%%~2!"
for %%a in ("!LF!") do set "var=!var:%%~a=%%~L!"
for %%a in ("!CR!") do set "var=!var:%%~a=%%~3!"

rem ** It is neccessary to use two IF else the %var% expansion doesn't work as expected
if not defined NotDelayedFlag set "var=!var:^=^^^^!"
if not defined NotDelayedFlag set "var=%var:!=^^^!%" !

set "replace=%% """ !CR!!CR!"
for %%L in ("!LF!") do (
   for /F "tokens=1,2,3" %%1 in ("!replace!") DO (
     ENDLOCAL
     ENDLOCAL
     set "%~1=%var%" !
     @echo off
      goto :eof
   )
)
exit /b

Ответ 4

Что-то вроде следующего (я его не тестировал):

@echo off 
setlocal 

set base=compute directory 
set pkg=compute sub-directory 
set scripts=%base%\%pkg%\Scripts 

pushd %scripts%

endlocal 

call .\activate.bat 
popd

Так как вышеупомянутое не работает (см. комментарий Марсело), ​​я, вероятно, сделаю это следующим образом:

set uniquePrefix_base=compute directory 
set uniquePrefix_pkg=compute sub-directory 
set uniquePrefix_scripts=%uniquePrefix_base%\%uniquePrefix_pkg%\Scripts 
set uniquePrefix_base=
set uniquePrefix_pkg=

call %uniquePrefix_scripts%\activate.bat
set uniquePrefix_scripts=

где uniquePrefix_ выбрано как "почти наверняка" уникальным в вашей среде.

Вы также можете проверить при входе в файл bat, что переменные окружения "uniquePrefix_..." undefined на входе, как и ожидалось, - если вы не можете выйти с ошибкой.

Мне не нравится копировать BAT в каталог TEMP в качестве общего решения из-за (a) вероятности состояния гонки с помощью > 1 вызывающего абонента, и (b) в общем случае BAT файл может иметь доступ к другим файлы с использованием пути относительно его местоположения (например,% ~ dp0..\somedir\somefile.dat).

Следующее уродливое решение решит (b):

setlocal

set scripts=...whatever...
echo %scripts%>"%TEMP%\%~n0.dat"

endlocal

for /f "tokens=*" %%i in ('type "%TEMP%\%~n0.dat"') do call %%i\activate.bat
del "%TEMP%\%~n0.dat"

Ответ 5

Я тоже хочу внести свой вклад в это и рассказать вам, как вы можете передать массив, похожий на массив:

@echo off
rem clean up array in current environment:
set "ARRAY[0]=" & set "ARRAY[1]=" & set "ARRAY[2]=" & set "ARRAY[3]="
rem begin environment localisation block here:
setlocal EnableExtensions
rem define an array:
set "ARRAY[0]=1" & set "ARRAY[1]=2" & set "ARRAY[2]=4" & set "ARRAY[3]=8"
rem 'set ARRAY' returns all variables starting with 'ARRAY':
for /F "tokens=1,* delims==" %%V in ('set ARRAY') do (
    if defined %%V (
        rem end environment localisation block once only:
        endlocal
    )
    rem re-assign the array, 'for' variables transport it:
    set "%%V=%%W"
)
rem this is just for prove:
for /L %%I in (0,1,3) do (
    call echo %%ARRAY[%%I]%%
)
exit /B

Код работает, потому что самый первый элемент массива запрашивается, if defined в блоке setlocal где он фактически определен, поэтому endlocal выполняется только один раз. Для всех последовательных итераций цикла блок setlocal уже завершен и, следовательно, if defined оценивается как FALSE.

Это основано на том факте, что по крайней мере один элемент массива назначен, или на самом деле, что есть хотя бы одна переменная, имя которой начинается с ARRAY, в блоке setlocal/endlocal. Если там ничего не существует, endlocal не будет выполняться. За пределами блока setlocal такая переменная не должна определяться, потому что в противном случае, if defined значение TRUE превышает один раз, и, следовательно, endlocal выполняется несколько раз.

Чтобы преодолеть эти ограничения, вы можете использовать флагоподобную переменную, в соответствии с этим:

  • очистите переменную флага, скажем, ARR_FLAG, перед командой setlocal: set "ARR_FLAG=";
  • определить переменную flag внутри блока setlocal/endlocal, то есть присвоить ему непустое значение (предпочтительно непосредственно перед циклом for/F): set "ARR_FLAG=###";
  • изменить, if defined командной строки: if defined ARR_FLAG ( оно if defined ARR_FLAG (;
  • тогда вы также можете сделать дополнительно:
    • изменить строку параметра for/F на "delims=";
    • измените set командную строку в цикле for/F на: set "%%V";

Ответ 6

Для выживания нескольких переменных: если вы решили пойти с "классическим"
ENDLOCAL & SET VAR=%TEMPVAR% упомянутый иногда в других ответах здесь (и удовлетворены тем, что недостатки, показанные в некоторых ответах, устранены или не являются проблемой), обратите внимание, что вы можете сделать несколько переменных, например ENDLOCAL & SET var1=%local1% & SET var2=%local2%.

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

Документы: https://ss64.com/nt/endlocal.html.

Ответ 7

Чтобы ответить на мой собственный вопрос (если нет другого ответа, и чтобы избежать повторений того, о котором я уже знаю)...

Создайте временный командный файл перед вызовом endlocal, который содержит команду для вызова целевого пакетного файла, затем вызовите и удалите его после endlocal:

echo %scripts%\activate.bat > %TEMP%\activate.bat

endlocal

call %TEMP%\activate.bat
del %TEMP%\activate.bat

Это так уродливо, я хочу испортить мне голову. Лучшие ответы приветствуются.

Ответ 8

Как насчет этого.

@echo off
setlocal
set base=compute directory
set pkg=compute sub-directory
set scripts=%base%\%pkg%\Scripts
(
  endlocal
  "%scripts%\activate.bat"
)