REM test.bat
#ECHO OFF
IF NOT "%~1" == "" IF NOT "%~2" == "" (
SET /A "X=%~1+%~2"
echo %X%
)
Instead of getting the sum of the two numbers, it always prints the previous value of X:
>test.bat 1 2
ECHO is off
>test.bat 3 4
3
>test.bat 4 4
7
Is that because I'm not using EnableDelayedExpansion?
Edit: Why does it work when I move the echo out of the scope?
#ECHO OFF
IF NOT "%~1" == "" IF NOT "%~2" == "" (
SET /A "X=%~1+%~2"
GOTO why
)
GOTO :EOF
:why
echo %X%
As you pointed the delayed expansion - I suppose you know how to fix it - the only (afaik) reason to want to avoid it's usage is if you want the variables to survive endlocal statement and to use the variable value in brackets context meanwhile .So you can use additional CALL or tunneling:
#ECHO OFF
:: using call
IF NOT "%~1" == "" IF NOT "%~2" == "" (
SET /A "X=%~1+%~2"
call echo %%X%%
)
#ECHO OFF
:: using tunneling
setlocal enableDelayedExpansion
IF NOT "%~1" == "" IF NOT "%~2" == "" (
SET /A "X=%~1+%~2"
rem echo !X!
)
endlocal & set x=%x%
echo %x%
EDIT WAS MADE
The call starts a new sub-context of the command prompts with inherited variables.Additional %s are used to expand the variable in the next level.
Why does it work when I move the echo out of the scope?
#ECHO OFF
IF NOT "%~1" == "" IF NOT "%~2" == "" (
SET /A "X=%~1+%~2"
GOTO why
)
GOTO :EOF
:why
echo %X%
GOTO breaks the brackets context ,but in this case even without GOTO the correct value of the %x% will be printed because is outside of the brackets.
Related
scripters,
at first sorry for the last post.
Realy, i hope i could make you understand my problem now.
I realized that the problem is inside my script only.
If i run the line in the command prompt it works fine.
That means the Errorlevel is stored to exitCode.txt correctly.
START /B "some title" cmd /C "subroutine.bat& >"exitCode.txt" (call echo %^errorlevel%)& >"status.txt" (echo done) & (if defined return call echo %^return%) >"return.txt"" 1>"stdOut.txt" 2>"stdErr.txt"
This is a part of my script
So in :treath.run is the START /B .... line
Inside the script the errorlevel is not stored correctly inside the definded file.
Why?
The return Value is stored perfect.
I dont't get it.
Save this as main.bat
#echo off
setlocal ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS
pushd "%~dp0"
:main
call :treath.import
call :treath.init "t1" "call subroutine.bat"
call :treath.run "t1"
:loop
call :treath.status "t1"
if "!return!"=="done" goto done
echo !return!
goto loop
:done
echo Now goto "!%t1%.stdExitCode!"
echo.
echo The Subroutine should return Errorlevel 1
echo But it is 0, in the file.
pause
EXIT
goto main
:::::::::::::::::::::::::::::::::::::::
:treath.import ()
set "return="
setlocal
set "objID=__OBJ_TREATH"
set "workdir=%tmp%\BATCH_OBJ\%objID%"
if not exist "%workdir%" (mkdir "%workdir%" || exit /b 1)
set /a "timeout=999"
:newSessionID
set /a "sessionID=%random%"
set "sessionDir=%workdir%\%sessionID%"
if exist "%sessionDir%" (
set /a "timeout-=1"
if !timeout! LSS 0 exit /b 1
goto newSessionID
)
mkdir "%sessionDir%" || exit /b 1
endlocal & (
rem set "%objID%_identifier=%objID%"
set "%objID%_version=0"
set "%objID%_sessionID=%sessionID%"
set "%objID%_sessionDir=%sessionDir%"
)
exit /b 0
:treath.init (treathName,"command")
set "return="
setlocal
rem hardcoded obj ident
set "objID=__OBJ_TREATH"
rem objID
set "prefix=%objID%_%~1"
rem treath folder
set "workdir=!%objID%_sessionDir!\%~1"
mkdir "%workdir%" || exit /b 1
set "command=%~2"
endlocal & (
set "%prefix%.command=%command%"
set "%prefix%.workdir=%workdir%"
set "%prefix%.stdOut=%workdir%\stdOut.txt"
set "%prefix%.stdErr=%workdir%\stdErr.txt"
set "%prefix%.stdDump=%workdir%\stdDump.txt"
set "%prefix%.stdExitCode=%workdir%\stdExitCode.txt"
set "%prefix%.return=%workdir%\return.txt"
set "%prefix%.stdStatus=%workdir%\stdStatus.txt"
set "%~1=%prefix%"
)
exit /b 0
:treath.run (treathName)
set "return="
setlocal
set "title=%~1"
set "prefix=!%~1!"
type NUL >"!%prefix%.stdExitCode!"
type NUL >"!%prefix%.stdOut!"
type NUL >"!%prefix%.stdErr!"
type NUL >"!%prefix%.stdStatus!"
rem errorlevel ist immer 0 ???
START /B "%title%" cmd /C "!%prefix%.command!& >"!%prefix%.stdExitCode!" (call echo %%^errorlevel%%)& >"!%prefix%.stdStatus!" (echo done) & (if defined return call echo %%^return%%) >"!%prefix%.return!"" 1>"!%prefix%.stdOut!" 2>"!%prefix%.stdErr!"
>"!%prefix%.stdStatus!" (echo running)
exit /b 0
:treath.status (treathName)
set "return="
setlocal
set "prefix=!%~1!"
endlocal & (
set /p "return="<"!%prefix%.stdStatus!" || exit /b 1
)
exit /b 0
Save this as "subroutine.bat"
set "return=This is a Test"
exit /b 1
I am trying to add a time out to Set /p Var1= So far I have only found this piece of code from here.
#echo off
setlocal EnableDelayedExpansion
if "%1" NEQ "" goto %1
del enter.tmp 2>nul >nul
start /b cmd /c %0 :secondThread
:FirstThread
set n=0
echo Enter Text (5 seconds timeout):
:loop
set /a n+=1
ping -n 2 localhost > nul
if !n! LSS 5 (
if not exist entER.tmp goto :loop
< Enter.tmp (
set /p input=
)
echo !input!
) ELSE (
echo Timeout for input
)
exit /b
:secondThread
set /p var=
> enter.tmp echo !var!
exit /b
This piece of code works great except it stops the count down only when the input is entered. I would like the count down to stop when any key is pressed. I don't know if this is possible. Thanks
I am currently making a game that has a persuasion system in it. I had all the code for one of the systems set up, but then I set up 2 more, and it started give me an error that said '(number) was not expected at this time'. when I put in 2 for the second choice, and 3 for the 3rd choice.
The code is like this.
#echo off
SETLOCAL EnableDelayedExpansion
for /F "tokens=1,2 delims=#" %%a in ('"prompt #$H#$E# & echo on & for %%b in (1) do rem"') do (
set "DEL=%%a"
)
set name=Quantum
cls
color 0a
Echo King Redwood: So 2000?
pause >nul
echo.
call :colorText 09 "1. 2500"
echo.
call :colorText 0e "2. 3000"
echo.
call :colorText 0c "3. 4000"
echo.
echo 4. 2000
echo.
set /p "purs=Enter:"
if /i %purs% == 1 (
goto CheckB )
if /i %purs% == 2 (
goto CheckY )
if /i %purs% == 3 (
goto CheckR )
if /i %purs% == 4 (
goto Convo )
:CheckB
set bleu=%random:~-2,1%
if %bleu% GTR 10 (
goto CheckB )
if %bleu% LSS 0 (
goto CheckB )
set /a num = 3
set /a reward = 2500
goto Res
:CheckY
set Yel=%random:~-2,1%
if %Yel% GTR 10 (
goto CheckY )
if %Yel% LSS 0 (
goto CheckY )
set /a num = 5
set reward = 3000
goto Res
:CheckR
set red=%random:~-2,1%
if %red% GTR 10 (
goto CheckB )
if %red% LSS 0 (
goto CheckB )
set /a num = 7
set /a reward = 4000
goto Res
:Convo
set /a reward = 2000
Echo %name%: I think that is a reasonable price.
Echo King Redwood: Very well.
Echo King Redwood: We will now take you to make sure you are
echo ready.
pause >nul
:Res
if %bleu% GEQ %num% goto Succeed
if NOT %bleu% GEQ %num% goto Fail
:Succeed
Echo %name%: I think that the struggles for such a long trip will be more then that
Echo %name%: How about %reward%?
Echo King Redwod: OK %reward% will work.
pause >nul
goto end
:Fail
Echo %name%: I think that you can give me %reward%.
Echo %name%: You know, for the struggles that there will be along the way.
echo If 2000 isn't good enough for you, I'll just have someone else do it.
pause >nul
:end
exit
:colorText
echo off
<nul set /p ".=%DEL%" > "%~2"
findstr /v /a:%1 /R "^$" "%~2" nul
del "%~2" > nul 2>&1i
First, make sure to close the FOR loop by putting a ) before :CheckB.
For the 'was not expected at this time' error, you're sometimes comparing an empty variable to something. For example, by following CheckY, you set Yel, then proceed to Res and check Bleu, which is empty because it hasn't been set. You're putting nothing next to the binary GEQ operator, and that's why it's complaining.
Tip: to debug, try inserting ECHO statements like this:
:Res
echo bleu=%bleu%,num=%num%
Another problem: when using SET, do not surround the = by spaces. SET /a will work with spaces around =, just because of the nature of /a, but plain SET will not. Well, it will append your variable name with a space and prepend your value with a space, which is not what you want.
Another tip: you can constrain what RANDOM returns through SET /a and the modulus operator, like this.
SET /a red=%random% %% 11
This will set red to a number between 0 and 10, so there is no need for the substrings and goto routines you're using after picking your random number.
Also, consider using EXIT /b to exit the batch file and not the whole CMD environment.
How can I check that the first character of a string is a letter and so that it is not a number, or rather a cipher? There are no spaces or special characters in this string.
#ECHO OFF
SETLOCAL
SET /a num=5678
CALL :initnum
SET "num=hello"
CALL :initnum
SET "num=4ello"
CALL :initnum
SET "num=hell0"
CALL :initnum
SET "num=he8lo"
CALL :initnum
SET "num="
CALL :initnum
ECHO(==============
SET /a nam=7654
SET "nem=hello"
SET "nim=4ello"
SET "nom=hell0"
SET "num=he8lo"
SET "nzm="
CALL :initnum2 nam
CALL :initnum2 nem
CALL :initnum2 nim
CALL :initnum2 nom
CALL :initnum2 num
CALL :initnum2 nzm
GOTO :EOF
:initnum
IF NOT DEFINED num ECHO NUM is empty, so it doesn't begin with a numeric&GOTO :EOF
FOR /l %%a IN (0,1,9) DO IF %num:~0,1%==%%a ECHO %num% Begins with numeric&GOTO :EOF
ECHO %num% Does NOT begin with a numeric
GOTO :eof
:initnum2
IF NOT DEFINED %1 ECHO %1 is empty, so it doesn't begin with a numeric&GOTO :EOF
CALL SET "$1=%%%1%%"
FOR /l %%a IN (0,1,9) DO IF %$1:~0,1%==%%a ECHO %1 (%$1%) Begins with numeric&GOTO :EOF
ECHO %1 (%$1%) Does NOT begin with a numeric
GOTO :eof
You should be able to get what you want from this demo.
#echo off
setlocal enableextensions disabledelayedexpansion
set "var=1hello"
for /f "tokens=* delims=0123456789" %%a in ("%var%") do (
if not "%%a"=="%var%" echo var starts with a number
)
If the var contents starts with a number, the token/delim management in the for command will remove it.
edited just to include the usual (included the previous code) and some less used options just in case someone is interested
#echo off
setlocal enableextensions disabledelayedexpansion
set "var=1hello"
echo(%var%
rem Option 1 - Use the for command to tokenize the string
rem A dot is added to handle empty vars
for /f "tokens=* delims=0123456789" %%a in ("%var%.") do (
if not "%%a"=="%var%." (
echo var starts with a number
) else (
echo var does not start with a number
)
)
rem Option 2 - Use set arithmetic and detect errors
rem This will fail if the string starts with + or -
set "%var%_=0"
set /a "test=%var%_" 2>nul
if not errorlevel 1 (
echo var does not start with a number
) else (
echo var starts with a number
)
rem Option 3 - Use substring operations and logic operators
set "test=%var%."
if "%test:~0,1%" GEQ "0" if "%test:~0,1%" LEQ "9" set "test="
if defined test (
echo var does not start with a number
) else (
echo var starts with a number
)
rem Option 4 - Use findstr
rem This is SLOW as findstr needs to be executed
echo(%var%|findstr /b /r /c:"[0-9]" >nul && (
echo var starts with a number
) || (
echo var does not start with a number
)
I think this is the simplest way:
#echo off
setlocal EnableDelayedExpansion
set digits=0123456789
set var=1something
if "!digits:%var:~0,1%=!" neq "%digits%" (
echo First char is digit
) else (
echo First char is not digit
)
The first character of var is tried to be removed from digits string. If such a char was a digit, digits string change; otherwise, digits string remains the same.
#echo off
setlocal
set "the_string=a23something"
for /l %%a in (%the_string% ; 1 ; %the_string%) do set "cl_string=%%~a"
if %the_string:~0,1% neq 0 if "%cl_string%" equ "0" (
echo does not start with number
) else (
echo starts with number
)
endlocal
Another approach is with FINDSTR which eventually will be slower as it is an external for cmd.exe command.
#echo off
set "the_string=something"
echo %the_string%|findstr /b /r "[0-9]" >nul 2>&1 && (
echo starts with number
) || (
echo does not start with number
)
This will work in your situation:
echo %variable%|findstr "^[a-zA-Z]" >nul && echo it starts with an alpha character
Using findstr with regexp :
#echo off
set "$string=2toto"
echo %$string:~0,1%|findstr /i "^-*0*x*[0-9][0-9]*$">nul && echo is NUM || echo Is not NUM
in place of echo is NUM or echo is not NUM you can use a goto to redirect your script the way you want it.
#echo off
set "$string=2toto"
echo %$string:~0,1%|findstr /i "^-*0*x*[0-9][0-9]*$">nul && goto:isnum || goto:isnotnum
:isnum
echo is NUM
exit/b
:isnotnum
echo is not NUM
You have to set the string as a variable; in this way you are able to extract substrings from a main string. Here is an example:
#echo off
set EXAMPLESTRING=12345abcde
set FIRSTCHARACTERSTRING=%EXAMPLESTRING:~0,1%
The result of this short script should be 1 in this case.
Then, you can set a series of conditions to verify whether the first character is a number or not:
if %FIRSTCHARACTERSTRING%==0 goto NUMBER
if %FIRSTCHARACTERSTRING%==1 goto NUMBER
if %FIRSTCHARACTERSTRING%==2 goto NUMBER
if %FIRSTCHARACTERSTRING%==3 goto NUMBER
if %FIRSTCHARACTERSTRING%==4 goto NUMBER
if %FIRSTCHARACTERSTRING%==5 goto NUMBER
if %FIRSTCHARACTERSTRING%==6 goto NUMBER
if %FIRSTCHARACTERSTRING%==7 goto NUMBER
if %FIRSTCHARACTERSTRING%==8 goto NUMBER
if %FIRSTCHARACTERSTRING%==9 goto NUMBER
goto LETTER
:NUMBER
echo The first character is a number!
goto EOF
:LETTER
echo The first character is a letter!
goto EOF
Maybe this is not the most efficient solution but it works fine and it is easier to understand.
Here is something I'm struggeling with:
Title Scanning online computers: && set /a title_count=0
call :next
:next
FOR /F "tokens=1" %%a IN (workstation.txt) do (
title Scanning for online computers: %title_count% / %workstation%
ping -n 1 %%a | find "bytes=" >nul
set /a title_count=title_count+=1
if NOT ERRORLEVEL 1 (
set color=%%a && call includes\what.bat %color_pass% && echo %%a >> logs\reachable.txt
) else (
set color=%%a && call includes\what.bat %color_fail% && echo %%a >> logs\unreachable.txt && echo %%a, offline, % date%, %time% >> logs\offline.txt
)
)
The problem I have here is that the function TITLE is not getting updated while the variable %count_title% is counting up through the script.
set /a title_count+=1
doesn't work either
It's displayed as:
Scanning for online computers 0 / 5
Can sombody tell me what I'm doing wrong here?
Thanks in advance.
Illusion
Hi,
I've tried it the way as suggested:
It finishes the rest of the script when using the last GOTO :EOF
IT doesn't make sense to me, if I remove the last GOTO :eof, only the first row in workstation.txt is getting processed/parsed.
Scanning online computers: && set /a title_count+=1`
call :next
::added as possibly missing
GOTO :EOF
:next
FOR /F "tokens=1" %%a IN (workstation.txt) DO CALL :pingstation %%a
GOTO :EOF
:pingstation
title Scanning for online computers: %title_count% / %workstation%
ping -n 1 %1 | find "bytes=" >nul
set /a title_count+=1
if NOT ERRORLEVEL 1 (
set color=%1 && call includes\what.bat %color_pass% && echo %1 >> logs\reachable.txt
) else (
set color=%1 && call includes\what.bat %color_fail% && echo %1 >> logs\unreachable.txt && echo %1, offline, %date%, %time% >> logs\offline.txt
)
goto :eof
)
Read this: Environment variable expansion occurs when the command is read.
Salient points:
Your variables are expanded right when for command (and its entire body enclosed in parentheses) is parsed.
Use !VARNAME! instead of %VARNAME% to avoid it.
For better portability across OS versions/setups, it's a good idea to stick a setlocal EnableExtensions EnableDelayedExpansion at the beginning of your batch file.
Also, make sure there is a goto (e.g., goto :EOF) after call :next, because the code as posted will run through next one extra time.
You can go with setlocal EnableDelayedExpansion and changing the % syntax to the ! one when addressing vars inside the loop that are initialised within that very loop, just as atzz has suggested.
But there's a different approach. You can simply move the body of the loop to a(nother) subroutine. That way the variables would expand as expected.
Title Scanning online computers: && set /a title_count=0
call :next
::added as possibly missing
GOTO :EOF
:next
FOR /F "tokens=1" %%a IN (workstation.txt) DO CALL :pingstation %%a
GOTO :EOF
:pingstation
title Scanning for online computers: %title_count% / %workstation%
ping -n 1 %1 | find "bytes=" >nul
set /a title_count+=1
if NOT ERRORLEVEL 1 (
set color=%1 && call includes\what.bat %color_pass% && echo %1 >> logs\reachable.txt
) else (
set color=%1 && call includes\what.bat %color_fail% && echo %1 >> logs\unreachable.txt && echo %1, offline, %date%, %time% >> logs\offline.txt
)