2010-07-16 13 views
21

Tengo un archivo por lotes que calcula una variable a través de una serie de variables intermedias:Hacer una variable de entorno sobrevivir ENDLOCAL

@echo off 
setlocal 

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

endlocal 

%scripts%\activate.bat 

El guión en la última línea no se llama, ya que se produce después de endlocal, que anula la variable de entorno scripts, pero tiene que venir después de endlocal porque su propósito es establecer un grupo de otras variables de entorno para que las use el usuario.

¿Cómo llamo a un script cuyo propósito es establecer variables de entorno permanentes, pero cuya ubicación está determinada por una variable de entorno temporal?

Sé que puedo crear un archivo de lotes temporal antes de endlocal y llamarlo después de endlocal, lo cual haré si nada más sale a la luz, pero me gustaría saber si hay una solución menos arriesgada.

+1

Me encantan todas las respuestas a continuación que muestran cómo manejar todo tipo de casos extremos que no entrarán en juego con su pregunta específica. (El resultado final debe ser un directorio legal, por lo que es poco probable que tenga '!' O ';' u otras rarezas de metacaracteres). Demuestra que a todos nos preocupa más la enseñanza que simplemente contentarnos rápidamente con una respuesta. Y eso, mis amigos, es StackOverflow. –

+0

Respondí esto usando el registro reg.exe y el volátil aquí: https://stackoverflow.com/questions/26246151/setlocal-enabledelayedexpansion-causes-cd-and-pushd-to-not-persist/45369743#45369743 – mosh

+0

Curiosamente , Sospecho que el script llamado solo necesita referenciar su propia ruta, IE, "Activate.bat" necesita saber de dónde fue llamada, y usar esa variable asignada como "Scripts" para futuras llamadas dentro de sí misma. –

Respuesta

20

El patrón ENDLOCAL & SET VAR=%TEMPVAR% es clásico. Pero hay situaciones en las que no es ideal.

Si no conoce el contenido del TempVar, a continuación, usted podría encontrarse con problemas si el valor contiene caracteres especiales como <>& o |. En general, puede protegerse contra eso mediante el uso de comillas como SET "VAR=%TEMPVAR%", pero eso puede causar problemas si hay caracteres especiales y el valor ya está citado.

Una expresión FOR es una excelente opción para transportar un valor a través de la barrera ENDLOCAL si le preocupan los caracteres especiales. La expansión retrasada debería estar habilitada antes de la ENDLOCAL y deshabilitada después de la ENDLOCAL.

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

Limitaciones:

  • Si expansión retardada se habilita después de la ENDLOCAL, entonces el valor final será dañada si la TempVar contenía !.

  • valores que contienen un carácter de avance de línea no pueden ser transportados

Si debe devolver varios valores, y usted sabe de un carácter que no puede aparecer en cualquiera de los valores, a continuación, sólo tiene que utilizar el apropiado para las opciones/F. Por ejemplo, si sé que los valores no pueden contener |:

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" 
) 

Si debe devolver múltiples valores, y el juego de caracteres no está restringido, a continuación, utilizar For anidados/F bucles:

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" 
) 
) 

Definitivamente echa un vistazo a jeb's answer para una técnica segura, a prueba de balas que funciona para todos los valores posibles en todas las situaciones.

2017-08-21 - Nueva función RETURN.BAT
He trabajado con DosTips usuario jeb para desarrollar a batch utility called RETURN.BAT que se puede utilizar para salir de la secuencia de comandos o llama programación y retornar una o más variables a través de la barrera ENDLOCAL. Muy bueno :-)

A continuación se muestra la versión 3.0 del código. Lo más probable es que no mantenga este código actualizado. Lo mejor es seguir el enlace para asegurarse de obtener la última versión y ver ejemplos de uso.

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

Por cierto. La sintaxis extraña es superflua y falla con un tempvar vacío. Mejor es 'FOR/F" delims = "%% A in (" "! Tempvar!" ") Endlocal & set" var = %% ~ A "' – jeb

+1

@jeb - ¿Qué tan superfluo? Si el valor comienza con ';', entonces se saltará. Buena idea con las citas adjuntas. Otra opción es inicializar el valor en nada antes de emitir SETLOCAL, excepto que puede fallar si se devuelven múltiples valores a través de múltiples bucles FOR/F. – dbenham

+1

Tal como está, no es superfluo, sino que se trata de la versión de doble cita, ya que no hay ningún problema con el carácter eol ni con los valores vacíos. – jeb

1

algo como lo siguiente (no he probado):

@echo off 
setlocal 

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

pushd %scripts% 

endlocal 

call .\activate.bat 
popd 

Dado lo anterior no funciona (ver el comentario de Marcelo), probablemente haría esto de la siguiente manera:

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= 

donde uniquePrefix_ se elige para ser "casi seguro" único en su entorno.

También podría probar al ingresar al archivo bat que las variables de entorno "uniquePrefix _..." no están definidas en la entrada como se esperaba, de lo contrario, puede salir con un error.

No me gusta copiar el BAT en el directorio TEMP como una solución general debido a (a) la posibilidad de una condición de carrera con> 1 llamante, y (b) en el caso general, un archivo BAT podría estar accediendo otros archivos que usan una ruta relativa a su ubicación (por ejemplo,% ~ dp0 .. \ somedir \ somefile.dat).

La siguiente solución fea va a resolver (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" 
+1

¡Una idea inspirada! Lo amo. Lamentablemente, no funciona. Lo intenté y descubrí que endlocal también revierte al directorio de trabajo antes de que setlocal fuera local. –

+0

Consulte [Respuesta anónima] (http://stackoverflow.com/questions/3262287/make-an-environment-variable-survive- endvelo/3262891#3262891) para obtener una buena forma de hacerlo. Se complica con más variables, pero funciona bien sin necesidad de crear ningún archivo. – Joey

0

para responder a mi propia pregunta (en caso de que ninguna otra respuesta viene a la luz, y para evitar repeticiones de la que ya sé) .. .

Crear un archivo por lotes temporal antes de llamar endlocal que contiene el comando para llamar al archivo por lotes de destino, a continuación, llamar y eliminarlo después endlocal:

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

endlocal 

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

Esto es tan feo, quiero colgar la cabeza avergonzado. Mejores respuestas son bienvenidas.

12
@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% 
+2

+1. Ese es. Funciona porque las variables de entorno se expanden mientras se analiza una línea, lo que significa que una vez que se ejecuta esta línea, primero se ejecutará 'endlocal' y luego se expandirá la variable en' set'. – Joey

+1

Pero la solución puede fallar, depende de los contenidos en% FOO%, consulte la respuesta de dbenham – jeb

8

El answer de dbenham es una buena solución para cadenas "normales", pero falla con signos de admiración ! expansión retardada si se habilita después de ENDLOCAL (dbenham Dicho esto también).

Pero siempre fallará con algunos contenidos complicados como avances de línea incrustados,
, ya que FOR/F dividirá el contenido en varias líneas.
Esto dará como resultado un comportamiento extraño, el endlocal se ejecutará varias veces (para cada alimentación de línea), por lo que el código no es a prueba de balas.

Existe soluciones a prueba de balas, pero son un poco desordenado :-)
Un macro versiónSO:Preserving exclamation ... existe, de usar, es fácil, pero para leerlo es ...

o podría utilice un bloque de código , puede pegarlo en sus funciones.
Dbenham y yo desarrollamos esta técnica en el hilo Re: new functions: :chr, :asc, :asciiMap,
hay también las explicaciones de esta técnica

@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's 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 
0

Quiero contribuir a esto también y le dirá cómo se puede pasar por encima de un conjunto tipo array de variables:

@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,2 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 

el código funciona, porque el primer elemento de la matriz se consulta por if defined dentro del bloque setlocal allí donde se define, por lo endlocal se ejecuta una sola vez. Para todas las iteraciones sucesivas de bucle, el bloque setlocal ya ha finalizado y, por lo tanto, if defined evalúa como FALSE.

Esto se basa en el hecho de que se asigna al menos un elemento de la matriz, o en realidad, de que hay al menos una variable definida cuyo nombre comienza con ARRAY, dentro del bloque setlocal/endlocal. Si no existe ninguno, endlocal no se ejecutará. Fuera del bloque setlocal, no debe definirse dicha variable, porque de lo contrario, if defined evalúa a TRUE más de una vez y, por lo tanto, endlocal se ejecuta varias veces.

Para superar esta restricción, se puede utilizar una variable de tipo bandera, de acuerdo con esto:

  • clara la variable bandera, ARR_FLAG decir, antes del comando setlocal: set "ARR_FLAG=";
  • define la variable de indicador dentro del bloque setlocal/endlocal, es decir, le asigna un valor no vacío (inmediatamente antes del lazo for /F, preferiblemente): set "ARR_FLAG=###";
  • cambie la línea de comando if defined a: if defined ARR_FLAG (;
  • , entonces también puede hacer opcionalmente:
    • cambio de la opción de cadena for /F a "delims=";
    • cambie la línea de comando set en el for /F bucle a: set "%%V";
-1

¿Qué tal esto.

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

¿Por qué no lo has probado antes de publicarlo? No funciona para nada – jeb

0

Para sobrevivir a múltiples variables: Si se elige el camino con el "clásico"
ENDLOCAL & VAR SET =% TempVar%
mencionado a veces en otras respuestas aquí (y están satisfechos de que los inconvenientes muestran en algunas de las respuestas se tratan o no son un problema), tenga en cuenta que se pueden hacer múltiples variables, a la
ENDLOCAL & SET var1 =% Local1% & SET var2 =% local2%
comparto esto porque aparte de la página enlazada abajo, solo he visto el "truco" ilustrado con un cantar le variable, y como yo, algunos pueden haber asumido incorrectamente que solo "funciona" para una sola variable. https://ss64.com/nt/endlocal.html

Cuestiones relacionadas