¿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?
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?
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 help
da 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 /p
si 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:
La variable de entorno
MyVar
no se modifica con el uso deset /P "MyVar=Your choice: "
si el usuario presiona intencionalmente o por error solo RETURNo ENTER. Esto significa que si la variable de entornoMyVar
no 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 siMyVar
ya 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 error1
cuando el usuario no ingresó ninguna cadena, como se documenta en ¿Cuáles son los valores ERRORLEVEL establecidos por los comandos internos cmd.exe?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 on
lugar de echo off
en 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.exe
encuentra 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 menu
que no está definida antes del mensaje del usuario y aún no está definida después del mensaje del usuario.
La cadena 2
se 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 menu
todaví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 menu
siempre 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 menu
ya no tiene efecto en la ejecución del archivo por lotes.
Otra solución sería hacer uso del código de salida 1
cuando 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 0
al 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 2
lo 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 /P
un 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+Echoice
Ctrl+C
La variable dinámica ERRORLEVEL
tiene tres opciones casi siempre un valor en el rango de 1 a 3 después choice
de 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.exe
NERRORLEVEL
0
if 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 ERRORLEVEL
es 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
.