batch-file - log - ss64 batch
¿Por qué falla la expansión retardada cuando se encuentra dentro de un bloque de código procesado? (3)
Aquí hay un archivo por lotes simple que demuestra cómo falla la expansión retrasada si está dentro de un bloque que se está canalizando. (El error está hacia el final del guión) ¿Alguien puede explicar por qué es esto?
Tengo una solución alternativa, pero requiere la creación de un archivo temporal. Inicialmente encontré este problema mientras trabajaba en Buscar archivos y ordenaba por tamaño en un archivo por lotes de Windows
@echo off
setlocal enableDelayedExpansion
set test1=x
set test2=y
set test3=z
echo(
echo NORMAL EXPANSION TEST
echo Unsorted works
(
echo %test3%
echo %test1%
echo %test2%
)
echo(
echo Sorted works
(
echo %test3%
echo %test1%
echo %test2%
) | sort
echo(
echo ---------
echo(
echo DELAYED EXPANSION TEST
echo Unsorted works
(
echo !test3!
echo !test1!
echo !test2!
)
echo(
echo Sorted fails
(
echo !test3!
echo !test1!
echo !test2!
) | sort
echo(
echo Sort workaround
(
echo !test3!
echo !test1!
echo !test2!
)>temp.txt
sort temp.txt
del temp.txt
Aquí están los resultados
NORMAL EXPANSION TEST
Unsorted works
z
x
y
Sorted works
x
y
z
---------
DELAYED EXPANSION TEST
Unsorted works
z
x
y
Sorted fails
!test1!
!test2!
!test3!
Sort workaround
x
y
z
¡Cosa graciosa! No sé la respuesta, lo que sé es que la operación de interconexión tiene fallas consistentes en Windows Batch que no deberían estar presentes en el lote original de MS-DOS (si tales características podrían ejecutarse en un lote de MS-DOS antiguo), entonces sospeche que el error se introdujo cuando se desarrollaron las nuevas características de Windows Batch.
Aquí hay unos ejemplos:
echo Value to be assigned | set /p var=
La línea anterior NO asigna el valor a la variable, por lo que debemos solucionarlo de esta manera:
echo Value to be assigned > temp.txt & set /p var=< temp.txt
Otro:
(
echo Value one
echo Value two
echo Value three
) | call :BatchSubroutine
No funciona Solucionarlo de esta manera:
(
echo Value one
echo Value two
echo Value three
) > temp.txt
call :BatchSubroutine < temp.txt
Sin embargo, este método SI funciona en ciertos casos; con DEBUG.COM por ejemplo:
echo set tab=9> def_tab.bat
(
echo e108
echo 9
echo w
echo q
) | debug def_tab.bat
call def_tab
echo ONE%tab%TWO
Programa previo show:
ONE TWO
¿En qué casos funciona y cual no? Solo God (y Microsoft) pueden saber, pero parece estar relacionado con las nuevas características de Windows Batch: comando SET / P, expansión retardada, bloque de código entre paréntesis, etc.
EDITAR: archivos por lotes asíncronos
NOTA : modifiqué esta sección para corregir un error mío. Vea mi último comentario para ver detalles.
Como dijo jeb, la ejecución de ambos lados de una tubería crea dos procesos asíncronos, que hacen posible la ejecución de subprocesos asíncronos incluso si no se utiliza el comando START
.
Mainfile.bat:
@echo off
echo Main start. Enter lines, type end to exit
First | Second
echo Main end
First.bat:
@echo off
echo First start
:loop
set /P first=
echo First read: %first%
if /I not "%first%" == "end" goto loop
echo EOF
echo First end
Second.bat:
@echo off
echo Second start
:loop
set /P second=Enter line:
echo Second read: %second%
echo/
if not "%second%" == "EOF" goto loop
echo Second end
Podemos usar esta capacidad para desarrollar un programa equivalente a la aplicación Expect (que funciona de manera similar al módulo pexpect Phyton ) que podría controlar cualquier programa interactivo de esta manera:
Input | anyprogram | Output
El archivo Output.bat alcanzará la parte "Esperar" al analizar la salida del programa, y Input.bat alcanzará la parte "Sendline" al proporcionar la entrada al programa. La comunicación hacia atrás desde los módulos de Salida a Entrada se logrará mediante un archivo con la información deseada y un sistema de semáforo simple controlado mediante la presencia / ausencia de uno o dos archivos de bandera.
Como muestra Aacini, parece que muchas cosas fallan dentro de una tubería.
echo hello | set /p var=
echo here | call :function
Pero en realidad es solo un problema entender cómo funciona la tubería.
Cada lado de un conducto inicia su propio cmd.exe en su propio subproceso asccrónico.
Esa es la causa por la cual muchas cosas parecen estar rotas.
Pero con este conocimiento puedes evitar esto y crear nuevos efectos
echo one | ( set /p varX= & set varX )
set var1=var2
set var2=content of two
echo one | ( echo %%%var1%%% )
echo three | echo MYCMDLINE %%cmdcmdline%%
echo four | (cmd /v:on /c echo 4: !var2!)
EDITAR: Análisis en profundidad
Como muestra dbenham, ambos lados de las tuberías son equivalentes para las fases de expansión.
Las reglas principales parecen ser:
Las fases normales del analizador de lotes están listas
.. porcentaje de expansión
.. la fase de caracter especial / bloque comienza la detección
.. demora en la expansión (pero solo si la expansión retrasada está habilitada Y no es un bloque de comandos)
Inicie el cmd.exe con C:/Windows/system32/cmd.exe /S /D /c"<BATCH COMMAND>"
Estas expansiones siguen las reglas del analizador de línea cmd, no del analizador de línea de proceso por lotes.
.. porcentaje de expansión
.. demora en la expansión (pero solo si la expansión retrasada está habilitada)
El <BATCH COMMAND>
se modificará si está dentro de un bloque de paréntesis.
(
echo one %%cmdcmdline%%
echo two
) | more
Llamado como C:/Windows/system32/cmd.exe /S /D /c" ( echo one %cmdcmdline% & echo two )"
, todas las nuevas líneas se cambian a &
operator.
¿Por qué la fase de expansión retrasada se ve afectada por paréntesis?
Supongo que no se puede expandir en la fase del analizador de lotes, ya que un bloque puede constar de muchos comandos y la expansión diferida tiene efecto cuando se ejecuta una línea.
(
set var=one
echo !var!
set var=two
) | more
Obviamente, el !var!
no se puede evaluar en el contexto del lote, ya que las líneas se ejecutan solo en el contexto de la línea cmd.
Pero, ¿por qué se puede evaluar en este caso en el contexto del lote?
echo !var! | more
En mi opinión, esto es un "error" o comportamiento inconsciente, pero no es el primero
EDITAR: Agregar el truco LF
Como lo muestra dbenham, parece haber alguna limitación a través del comportamiento cmd que cambia todas las alimentaciones de línea en &
.
(
echo 7: part1
rem This kills the entire block because the closing ) is remarked!
echo part2
) | more
Esto resulta en
C:/Windows/system32/cmd.exe /S /D /c" ( echo 7: part1 & rem This ...& echo part2 ) "
El rem
observará la línea de cola completa, por lo que incluso el corchete de cierre falta entonces.
¡Pero puede resolver esto incorporando sus propios canales de línea!
set LF=^
REM The two empty lines above are required
(
echo 8: part1
rem This works as it splits the commands %%LF%% echo part2
) | more
Esto resulta en C:/Windows/system32/cmd.exe /S /D /c" ( echo 8: part1 %cmdcmdline% & rem This works as it splits the commands %LF% echo part2 )"
Y como el% lf% se expande al analizar las parenthises por el analizador, el código resultante se ve como
( echo 8: part1 & rem This works as it splits the commands
echo part2 )
Este comportamiento %LF%
funciona siempre dentro de paréntesis, también en un archivo por lotes.
Pero no en líneas "normales", hay un único <linefeed>
que detendrá el análisis sintáctico para esta línea.
EDITAR: Asincrónicamente no es la verdad completa
Dije que ambos hilos son asíncronos, normalmente esto es cierto.
Pero, en realidad, el hilo izquierdo puede bloquearse cuando los datos canalizados no son consumidos por el hilo derecho.
Parece que hay un límite de ~ 1000 caracteres en el búfer "tubería", luego el hilo se bloquea hasta que se consumen los datos.
@echo off
(
(
for /L %%a in ( 1,1,60 ) DO (
echo A long text can lock this thread
echo Thread1 ##### %%a > con
)
)
echo Thread1 ##### end > con
) | (
for /L %%n in ( 1,1,6) DO @(
ping -n 2 localhost > nul
echo Thread2 ..... %%n
set /p x=
)
)
No estaba seguro de si debería editar mi pregunta, o publicar esto como una respuesta.
Ya sabía vagamente que un conducto ejecuta el lado izquierdo y el derecho cada uno en su propia "sesión" CMD.EXE. Pero las respuestas de Aacini y Jeb me obligaron a pensar realmente e investigar lo que está sucediendo con las tuberías. (¡Gracias, jeb, por demostrar lo que está pasando al conectar SET / P!)
Desarrollé este guión de investigación; ayuda a explicar mucho, pero también demuestra un comportamiento extraño e inesperado. Voy a publicar el script, seguido de la salida. Finalmente proporcionaré algunos análisis.
@echo off
cls
setlocal disableDelayedExpansion
set var1=value1
set "var2="
setlocal enableDelayedExpansion
echo on
@echo NO PIPE - delayed expansion is ON
echo 1: %var1%, %var2%, !var1!, !var2!
(echo 2: %var1%, %var2%, !var1!, !var2!)
@echo(
@echo PIPE LEFT SIDE - Delayed expansion is ON
echo 1L: %%var1%%, %%var2%%, !var1!, !var2! | more
(echo 2L: %%var1%%, %%var2%%, !var1!, !var2!) | more
(setlocal enableDelayedExpansion & echo 3L: %%var1%%, %%var2%%, !var1!, !var2!) | more
(cmd /v:on /c echo 4L: %%var1%%, %%var2%%, !var1!, !var2!) | more
cmd /v:on /c echo 5L: %%var1%%, %%var2%%, !var1!, !var2! | more
@endlocal
@echo(
@echo Delayed expansion is now OFF
(cmd /v:on /c echo 6L: %%var1%%, %%var2%%, !var1!, !var2!) | more
cmd /v:on /c echo 7L: %%var1%%, %%var2%%, !var1!, !var2! | more
@setlocal enableDelayedExpansion
@echo(
@echo PIPE RIGHT SIDE - delayed expansion is ON
echo junk | echo 1R: %%var1%%, %%var2%%, !var1!, !var2!
echo junk | (echo 2R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | (setlocal enableDelayedExpansion & echo 3R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | (cmd /v:on /c echo 4R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | cmd /v:on /c echo 5R: %%var1%%, %%var2%%, !var1!, !var2!
@endlocal
@echo(
@echo Delayed expansion is now OFF
echo junk | (cmd /v:on /c echo 6R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | cmd /v:on /c echo 7R: %%var1%%, %%var2%%, !var1!, !var2!
Aquí está la salida
NO PIPE - delayed expansion is ON
C:/test>echo 1: value1, , !var1!, !var2!
1: value1, , value1,
C:/test>(echo 2: value1, , !var1!, !var2! )
2: value1, , value1,
PIPE LEFT SIDE - Delayed expansion is ON
C:/test>echo 1L: %var1%, %var2%, !var1!, !var2! | more
1L: value1, %var2%, value1,
C:/test>(echo 2L: %var1%, %var2%, !var1!, !var2! ) | more
2L: value1, %var2%, !var1!, !var2!
C:/test>(setlocal enableDelayedExpansion & echo 3L: %var1%, %var2%, !var1!, !var2! ) | more
3L: value1, %var2%, !var1!, !var2!
C:/test>(cmd /v:on /c echo 4L: %var1%, %var2%, !var1!, !var2! ) | more
4L: value1, %var2%, value1, !var2!
C:/test>cmd /v:on /c echo 5L: %var1%, %var2%, !var1!, !var2! | more
5L: value1, %var2%, value1,
Delayed expansion is now OFF
C:/test>(cmd /v:on /c echo 6L: %var1%, %var2%, !var1!, !var2! ) | more
6L: value1, %var2%, value1, !var2!
C:/test>cmd /v:on /c echo 7L: %var1%, %var2%, !var1!, !var2! | more
7L: value1, %var2%, value1, !var2!
PIPE RIGHT SIDE - delayed expansion is ON
C:/test>echo junk | echo 1R: %var1%, %var2%, !var1!, !var2!
1R: value1, %var2%, value1,
C:/test>echo junk | (echo 2R: %var1%, %var2%, !var1!, !var2! )
2R: value1, %var2%, !var1!, !var2!
C:/test>echo junk | (setlocal enableDelayedExpansion & echo 3R: %var1%, %var2%, !var1!, !var2! )
3R: value1, %var2%, !var1!, !var2!
C:/test>echo junk | (cmd /v:on /c echo 4R: %var1%, %var2%, !var1!, !var2! )
4R: value1, %var2%, value1, !var2!
C:/test>echo junk | cmd /v:on /c echo 5R: %var1%, %var2%, !var1!, !var2!
5R: value1, %var2%, value1,
Delayed expansion is now OFF
C:/test>echo junk | (cmd /v:on /c echo 6R: %var1%, %var2%, !var1!, !var2! )
6R: value1, %var2%, value1, !var2!
C:/test>echo junk | cmd /v:on /c echo 7R: %var1%, %var2%, !var1!, !var2!
7R: value1, %var2%, value1, !var2!
Probé tanto el lado izquierdo como el derecho de la tubería para demostrar que el procesamiento es simétrico en ambos lados.
Las pruebas 1 y 2 demuestran que los paréntesis no tienen ningún impacto en la expansión retardada bajo circunstancias de lote normales.
Pruebas 1L, 1R: la expansión retardada funciona como se esperaba. Var2 no está definido, entonces% var2% y! Var2! El resultado demuestra que los comandos se ejecutan en un contexto de línea de comandos y no en un contexto de proceso por lotes. En otras palabras, se utilizan reglas de análisis de línea de comandos en lugar de análisis por lotes. (ver ¿Cómo funciona el intérprete de comandos de Windows (CMD.EXE) para analizar scripts? ) EDITAR -! VAR2! se expande en el contexto del lote padre
Pruebas 2L, 2R: ¡Los paréntesis inhabilitan la expansión retardada! Muy extraño e inesperado en mi mente. Editar - jeb considera esto un error de MS o un error de diseño. Estoy de acuerdo, no parece haber ninguna razón racional para el comportamiento inconsistente
Pruebas 3L, 3R: setlocal EnableDelayedExpansion
no funciona. Pero esto se espera porque estamos en un contexto de línea de comando. setlocal
solo funciona en un contexto por lotes.
Pruebas 4L, 4R: la expansión retrasada se habilita inicialmente, pero los paréntesis la desactivan. CMD /V:ON
vuelve a habilitar la expansión retrasada y todo funciona como se esperaba. Todavía tenemos contexto de línea de comando y el resultado es el esperado.
Pruebas 5L, 5R: Casi lo mismo que 4L, 4R, excepto que la expansión retardada ya está habilitada cuando se ejecuta CMD /V:on
. % var2% da salida de contexto de línea de comando esperada. Pero! Var2! la salida está en blanco, lo que se espera en un contexto de proceso por lotes. Este es otro comportamiento muy extraño e inesperado. Editar - en realidad esto tiene sentido ahora que lo sé! Var2! se expande en el contexto del lote padre
Pruebas 6L, 6R, 7L, 7R: Estas son análogas a las pruebas 4L / R, 5L / R, excepto que ahora la expansión retardada comienza desactivada. Esta vez, los 4 escenarios dan el esperado! Var2! salida del contexto del lote
Si alguien puede proporcionar una explicación lógica para los resultados de 2L, 2R y 5L, 5R, entonces seleccionaré esa como la respuesta a mi pregunta original. De lo contrario, probablemente acepte esta publicación como la respuesta (realmente más una observación de lo que sucede que una respuesta) Editar - jab clavado!
Adición: En respuesta al comentario de jeb, aquí hay más evidencia de que los comandos canalizados dentro de un lote se ejecutan en un contexto de línea de comando, no en un contexto de proceso por lotes.
Este script por lotes:
@echo on
call echo batch context %%%%
call echo cmd line context %%%% | more
da esta salida:
C:/test>call echo batch context %%
batch context %
C:/test>call echo cmd line context %% | more
cmd line context %%
Anexo final
He agregado algunas pruebas y resultados adicionales que demuestran todos los hallazgos hasta el momento. También demuestro que FOR expansión variable tiene lugar antes del procesamiento de la tubería. Finalmente, muestro algunos efectos secundarios interesantes del procesamiento de tuberías cuando un bloque multilínea se colapsa en una sola línea.
@echo off
cls
setlocal disableDelayedExpansion
set var1=value1
set "var2="
setlocal enableDelayedExpansion
echo on
@echo(
@echo Delayed expansion is ON
echo 1: %%, %%var1%%, %%var2%%, !var1!, ^^^!var1^^^!, !var2!, ^^^!var2^^^!, %%cmdcmdline%% | more
(echo 2: %%, %%var1%%, %%var2%%, !var1!, ^^^!var1^^^! !var2!, %%cmdcmdline%%) | more
for %%a in (Z) do (echo 3: %%a %%, %%var1%%, %%var2%%, !var1!, ^^^!var1^^^! !var2!, %%cmdcmdline%%) | more
(
echo 4: part1
set "var2=var2Value
set var2
echo "
set var2
)
(
echo 5: part1
set "var2=var2Value
set var2
echo "
set var2
echo --- begin cmdcmdline ---
echo %%cmdcmdline%%
echo --- end cmdcmdline ---
) | more
(
echo 6: part1
rem Only this line remarked
echo part2
)
(
echo 7: part1
rem This kills the entire block because the closing ) is remarked!
echo part2
) | more
Aquí está la salida
Delayed expansion is ON
C:/test>echo 1: %, %var1%, %var2%, !var1!, ^!var1^!, !var2!, ^!var2^!, %cmdcmdline% | more
1: %, value1, %var2%, value1, !var1!, , !var2!, C:/Windows/system32/cmd.exe /S /D /c" echo 1: %, %var1%, %var2%, value1, !var1!, , !var2!, %cmdcmdline% "
C:/test>(echo 2: %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% ) | more
2: %, value1, %var2%, !var1!, !var1! !var2!, C:/Windows/system32/cmd.exe /S /D /c" ( echo 2: %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% )"
C:/test>for %a in (Z) do (echo 3: %a %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% ) | more
C:/test>(echo 3: Z %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% ) | more
3: Z %, value1, %var2%, !var1!, !var1! !var2!, C:/Windows/system32/cmd.exe /S /D /c" ( echo 3: Z %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% )"
C:/test>(
echo 4: part1
set "var2=var2Value
set var2
echo "
set var2
)
4: part1
var2=var2Value
"
var2=var2Value
C:/test>(
echo 5: part1
set "var2=var2Value
set var2
echo "
set var2
echo --- begin cmdcmdline ---
echo %cmdcmdline%
echo --- end cmdcmdline ---
) | more
5: part1
var2=var2Value & set var2 & echo
--- begin cmdcmdline ---
C:/Windows/system32/cmd.exe /S /D /c" ( echo 5: part1 & set "var2=var2Value
var2=var2Value & set var2 & echo
" & set var2 & echo --- begin cmdcmdline --- & echo %cmdcmdline% & echo --- end cmdcmdline --- )"
--- end cmdcmdline ---
C:/test>(
echo 6: part1
rem Only this line remarked
echo part2
)
6: part1
part2
C:/test>(echo %cmdcmdline% & (
echo 7: part1
rem This kills the entire block because the closing ) is remarked!
echo part2
) ) | more
Pruebas 1: y 2: resumen todos los comportamientos, y el truco %% cmdcmdline %% realmente ayuda a demostrar lo que está sucediendo.
Prueba 3: demuestra que FOR expansión variable aún funciona con un bloque canalizado.
Pruebas 4: / 5: y 6: / 7: muestran los efectos secundarios interesantes de la forma en que las tuberías trabajan con bloques multilínea. ¡Tener cuidado!
Tengo que creer que descifrar las secuencias de escape dentro de escenarios complejos de tuberías será una pesadilla.