ss64 log how commands batch batch-file pipe

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.