¿Cómo evitar que el intérprete de comandos de Windows abandone la ejecución de archivos por lotes debido a una entrada incorrecta del usuario?

Resuelto Scribb 498 asked hace 6 años • 1 respuestas

Si tengo el siguiente código de ejemplo:

@echo off
:menu
cls
echo 1. win
echo 2. lose
set /p menu=
goto %menu%
pause>nul

:win
cls
echo yay lolz
pause>nul

:lose
cls
echo really?
pause>nul

¿Cómo evito que el lote se cierre si escribo "prueba" en lugar de una respuesta válida?

Scribb 498 avatar Apr 14 '18 22:04 Scribb 498
Aceptado

1. Documentación para los comandos de Windows

Sugiero marcar como favorito en su navegador:

  • Referencia de línea de comandos de Microsoft
  • Un índice AZ de la línea de comando CMD de Windows (SS64)

Se puede obtener ayuda para cada comando de Windows ejecutando en una ventana del símbolo del sistema el comando con /?como parámetro, por ejemplo if /?, set /?, ... La ejecución de helpda como resultado una lista incompleta de comandos de Windows con una breve descripción.


2. Uso de SET /P para indicaciones del usuario

Es aconsejable no utilizarlo set /psi el usuario debe elegir entre alguna de las varias opciones ofrecidas. Hay varios hechos que se deben tener en cuenta al solicitarle a un usuario que ingrese una cadena y la asigne a una variable de entorno:

  1. La variable de entorno MyVarno se modifica con el uso de set /P "MyVar=Your choice: "si el usuario presiona intencionalmente o por error solo RETURNo ENTER. Esto significa que si la variable de entorno MyVarno está definida antes del mensaje del usuario, aún no estará definida después de que el mensaje del usuario termine con solo presionar la tecla RETURN. Y si MyVarya está definido antes del mensaje del usuario, mantiene su valor sin modificar en caso de que el usuario presione solo RETURNo ENTER. El comando SET sale con un valor de error 1cuando el usuario no ingresó ninguna cadena, como se documenta en ¿Cuáles son los valores ERRORLEVEL establecidos por los comandos internos cmd.exe?

  2. El usuario tiene la libertad de escribir cualquier cadena cuando se le solicite set /P. El autor del archivo por lotes no tiene control sobre lo que realmente ingresa el usuario. Por lo tanto, el autor del archivo por lotes debe tener en cuenta que el usuario ingresa por error o intencionalmente una cadena que podría resultar en una salida de la ejecución del archivo por lotes debido a un error de sintaxis, o hace algo completamente diferente a lo definido.

Un ejemplo sencillo:

@echo on
:MainMenu
@set /P "menu=Your choice: "
if %menu% == 1 goto Label1
if %menu% == 2 goto Label2
goto MainMenu

:Label1
@echo Option 1 was chosen, fine.
exit /B

:Label2
@echo Option 2 was chosen, okay.

Este archivo por lotes en echo onlugar de echo offen la parte superior se inicia desde una ventana del símbolo del sistema para fines de depuración.

Simplemente RETURNse presiona cuando el usuario lo solicita en la primera ejecución. El intérprete de comandos de Windows sale del procesamiento del archivo por lotes porque la primera condición IF se preprocesa antes de la ejecución del comando IF para:

if  == 1 goto Label1

Evidentemente falta el primer argumento. cmd.exeencuentra este error de sintaxis y sale del procesamiento por lotes con un mensaje de error apropiado. El motivo es la falta de definición de la variable de entorno menuque no está definida antes del mensaje del usuario y aún no está definida después del mensaje del usuario.

La cadena 2se ingresa en la segunda ejecución del archivo por lotes desde la ventana del símbolo del sistema y el archivo por lotes funciona como se esperaba.

En la tercera ejecución del archivo por lotes desde la misma ventana del símbolo del sistema, simplemente RETURNse presiona nuevamente en el símbolo del usuario. El archivo por lotes genera nuevamente el segundo mensaje. ¿Por qué? La variable de entorno menutodavía está definida a partir de la ejecución del segundo archivo por lotes con esa cadena y la variable no se modificó al presionar RETURN.

Bien, modifiquemos el archivo por lotes de ejemplo para:

@echo on
:MainMenu
@set "menu=2"
@set /P "menu=Your choice: "
if "%menu%" == "1" goto Label1
if "%menu%" == "2" goto Label2
goto MainMenu

:Label1
@echo Option 1 was chosen, fine.
exit /B

:Label2
@echo Option 2 was chosen, okay.

Esto ya es mejor ya que ahora la variable de entorno menusiempre está predefinida con valor 2. Entonces, si el usuario no ingresa nada, se realiza un salto Label2. Además, el valor de la ejecución anterior de la variable menuya no tiene efecto en la ejecución del archivo por lotes.

Otra solución sería hacer uso del código de salida 1cuando el usuario no ingresa ninguna cadena y definir solo en este caso la variable de entorno con un valor predeterminado usando:

@set /P "menu=Your choice: " || set "menu=2"

Una sola línea con múltiples comandos usando un archivo por lotes de Windows describe el operador de ejecución condicional ||para ejecutar el segundo comando set "menu=2"solo si el primer comando ejecutado set /P "menu=Your choice: "sale con un código de salida diferente 0al que se hace cuando el usuario no ingresa nada en absoluto.

Gracias aschipfl por este aporte.

Pero, ¿es eso realmente seguro y a prueba de fallos ahora?

No, no lo es. El usuario aún puede ingresar por error una cadena incorrecta.

Por ejemplo, el usuario ingresa por error "en lugar de 2lo cual es fácil en los teclados alemanes CapsLock+2o Shift+2resulta en ingresar ". La primera línea de comando IF después del preprocesamiento ahora es:

if """ == "1" goto Label1

Y esta es nuevamente una línea de comando no válida que resulta en una salida del procesamiento de archivos por lotes debido a un error de sintaxis.

Supongamos que un usuario ingresa la cadena cuando se le solicita:

" == "" call dir "%USERPROFILE%\Desktop" & rem 

Nota: Hay un espacio al final.

La primera condición IF es preprocesada por el intérprete de comandos de Windows para:

if "" == "" call dir "%USERPROFILE%\Desktop"   & rem " == "1" goto Label1

Se puede ver que el archivo por lotes ejecuta ahora un comando que no está escrito en absoluto en el archivo por lotes en ambas condiciones IF .

¿Cómo hacer que un mensaje de usuario sea seguro y seguro?

Se puede hacer que un mensaje de usuario sea seguro y seguro mediante el uso de expansión variable retardada al menos para el código que evalúa la cadena ingresada por el usuario.

@echo on
:MainMenu
@setlocal EnableDelayedExpansion
@set "Label=MainMenu"
@set /P "menu=Your choice: " || set "menu=2"
if "!menu!" == "1" set "Label=Label1"
if "!menu!" == "2" set "Label=Label2"
endlocal & goto %Label%

:Label1
@echo Option 1 was chosen, fine.
exit /B

:Label2
@echo Option 2 was chosen, okay.

Ahora la cadena de entrada del usuario ya no modifica las líneas de comando ejecutadas por el procesador de comandos de Windows. Por lo tanto, ya no es posible salir del procesamiento de archivos por lotes debido a un error de sintaxis causado por la entrada del usuario (a prueba de fallos). Además, el archivo por lotes nunca ejecuta comandos que no estén escritos en el archivo por lotes (seguro).


3. Uso de CHOICE para un mensaje de elección

Hay un comando mejor que set /Pun menú de elección simple: ELECCIÓN .

@echo off
:MainMenu
cls
echo/
echo    1 ... Option 1
echo    2 ... Option 2
echo    E ... Exit
echo/
%SystemRoot%\System32\choice.exe /C 12E /N /M "Your choice: "
if errorlevel 3 exit /B
if errorlevel 2 goto Label2
if not errorlevel 1 goto MainMenu

@echo Option 1 was chosen, fine.
exit /B

:Label2
@echo Option 2 was chosen, okay.

El usuario ya no tiene libertad para ingresar algo que no esté definido por el autor del archivo por lotes. El archivo por lotes continúa inmediatamente después de que el usuario haya presionado 1, 2o . Todo lo demás es ignorado con excepción de .EShift+EchoiceCtrl+C

La variable dinámica ERRORLEVELtiene tres opciones casi siempre un valor en el rango de 1 a 3 después choicede terminar con el retorno de 1 a 3 como código de salida para llamar cmd.exe. La excepción es el caso de uso poco común en el que el usuario de un archivo por lotes presionó Ctrl+Cel mensaje y responde al siguiente mensaje Terminate batch job (Y/N)?con . En este caso, la variable dinámica tiene el valor que es la razón para manejar también este caso de uso tan especial.cmd.exeNERRORLEVEL0if not errorlevel 1 goto MainMenu

Nota: if errorlevel X significa SI MAYOR O IGUAL X . Por lo tanto, siempre es necesario comenzar con el código de salida de comando más alto posible choice.

Como el código de salida asignado ERRORLEVELes bien conocido, en menús más grandes es posible optimizar aún más el código utilizando etiquetas apropiadas:

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ERRORLEVEL="

:MainMenu
cls
echo/
echo    1 ... Option 1
echo    2 ... Option 2
echo    E ... Exit
echo/
%SystemRoot%\System32\choice.exe /C 12E /N /M "Your choice: "
goto Label%ERRORLEVEL%

:Label0
rem The user pressed Ctrl+C and on next prompt N and
rem so made no choice. Prompt the user once again.
goto MainMenu

:Label1
@echo Option 1 was chosen, fine.
exit /B

:Label2
@echo Option 2 was chosen, okay.
exit /B

:Label3

El uso del comando CHOICE puede hacer que los menús de elección sean muy sencillos de codificar.

La tercera línea de comando se asegura de que no se defina por casualidad una variable de entornoERRORLEVEL con nombre que de otro modo impediría acceder al valor actual de la variable dinámicaERRORLEVEL usando el código de salida del comando CHOICE usando la sintaxis %ERRORLEVEL%.

Nota: El uso de goto Label%ERRORLEVEL%solo es posible con las líneas de comando del menú de opciones que no están dentro de un bloque de comando que comienza (y termina con un correspondiente ).

Ver también: ¿Cómo puedo hacer que aparezca un mensaje "¿está seguro" en un archivo por lotes de Windows?

Sugerencia 1: CHOICE emite un pitido si el usuario presiona una tecla no aceptable. No es posible suprimir ese pitido ya que CHOICE no ofrece ninguna opción para evitar la emisión del pitido.

Pista 2: Consulte también mi respuesta sobre ¿ A dónde regresa GOTO :EOF? explicando también exit /B.

Mofi avatar Apr 14 '2018 17:04 Mofi