Batch script: local variable from function1 to function2 - windows

Okay guys, let my try to explain my problem:
I start with a line from where I start 2 different functions
setlocal EnableDelayedExpansion
for %%i in ("C:\*.*") do (
call :function1 "%%~i"
call :function2 "%%~i"
)
goto :eof
In function1, at a certain point I DO a SET in a local environment:
setlocal EnableDelayedExpansion
...
...
set name1=blabla
endlocal & SET name=%name1%
echo %name%
goto :eof
The echo does return my variable. Now onto my problem.
I quit function one and i go to function 2 (see first code-segment)
I can't call the variable form here. I tried in the function2, I tried before function2 is called, but both didn't resolve the issue.
My guess is a have set only a local variable for function1. I search the nets but i read that the line "endlocal & SET name=%name1%" should have solved my issue.
I hope I have explained it well, all help appreciated!

I'm not sure where your problem lies since this works perfectly:
#setlocal enableextensions enabledelayedexpansion
#echo off
set name=ORIGNAME
for %%i in (1 2) do (
call :function1 %%i
echo in main %name% !name!
)
endlocal
goto :eof
:function1:
setlocal enableextensions enabledelayedexpansion
set name1=%1_blabla
endlocal & SET name=%name1%
echo in function %name%
goto :eof
outputting:
in function 1_blabla
in main ORIGNAME 1_blabla
in function 2_blabla
in main ORIGNAME 2_blabla
Are you certain that, when you used name in the main areas, you used !name! instead of %name%?
If you used the %name% variant, that would be evaluated when the entire for loop was read, not at the time when you used it (in other words, it would be blank). You can see that in the output of ORIGNAME in the main line.
I'm not certain that's the case since you are using delayed expansion. But, just in case, I thought I'd mention it. I always use delayed expansion and I always used the ! variants of the environment variables since it more closely matches how I expect a shell to work.
In any case, the code I've given works fine so you may want to fiddle with that to see if you can incorporate it into your own.

First a small working sample
#echo off
setlocal EnableDelayedExpansion
call :myAdd returnVar 1 2
echo 1. Percent %returnVar%
echo 1. Exlcam !returnVar!
(
call :myAdd returnVar 4 5
echo 2. Percent %returnVar%
echo 2. Exlcam !returnVar!
)
goto :eof
:myAdd
setlocal
set /a result=%2 + %3
(
endlocal
set %1=%result%
goto :eof
)
---- Output ----
1. Percent 3
1. Exlcam 3
2. Percent 3
2. Exlcam 9
The cause for the wrong result in "2. Percent" is a result of the expanding of %var%.
They are expanded at the time of parsing, before executing any line of the parenthesis block, so there is the old value in returnVar.
But that is also the cause why the returning of variables from a function works.
the endlocal removes all local variables, but the block (an ampersand works the same way)
is expanded before the "endlocal" is executed.
It's a good idea to test this issues with "echo on".

Related

Windows Batch File Multiple labels back to back

In PHP or Javascript and other languages as well, there is the switch, case statement as a control flow tool. One of the neat features of that is it allows for multiple cases to be pointed to a single command group. For example:
switch(abc) {
case a:
case b:
case false:
console.log("hi")
break;
case c:
console.log("see ya")
break;
etc...
}
So that if abc is equal to a or b or is false, the "Hi" will be logged. Depending on the code, it can be a lot cleaner than calling from an object or tons of if else or if x || y || z statements.
I have a windows batch file where I'm doing the following:
GOTO %1
..... stuff
.... more stuff
REM =================LABELS BELOW==============
:-h
:--help
:-?
:--?
type help.txt
exit /b
It's more detailed than the above pseudocode, but that's the gist of it. It allows for aliases for the same argument And it works. If I execute mycmd -h or mycmd --help etc., I help the help file text displayed on the screen.
However, on the last line of my output, I get the error, THE SYSTEM CANNOT FIND THE BATCH LABEL SPECIFIED -.
The error might be caused by something else. I have some CALL commands and GOTO :EOF statements, so that certainly could be the source of the error.
But I've never seen the logic I applied above before used in a batch file, and I'm wondering if there are some other side effects that I might not be considering. Is it possible that I will encounter unpredictable side effects down the road? Is this bad practice for any reason?
Update:
I hadn't posted my code, because I think it's hard to read, and it's a work in progress. Basically, what you're seeing here is argument parsing. I'm parsing the passed values in to categories - imagine this example: node --file=d.js. I'm calling the --file the param, and the d.js the arg. And much of the code below is creating an array of each.
#echo off
SETLOCAL enabledelayedexpansion
#SET "T=%1"
IF /i NOT DEFINED T (GOTO -h)
SET /a counter=1
SET /a argCount=0
SET /a index=0
for %%x in (%*) do set /A argCount+=1
for /l %%x in (1,2,%argCount%) do (
call SET param[!index!]=%%!counter!
set /a counter+=2
SET /a index+=1
)
SET /A paramCount=!index!
SET /a counter=2
SET /a index=0
for /l %%x in (2,2,%argCount%) do (
call SET arg[!index!]="%%!counter!"
set /a counter+=2
SET /a index+=1
)
for /l %%i in (0,1,!paramCount!) do (
SET "arg=!ARG[%%i]!"
SET "p=!param[%%i]!"
CALL :!p! !arg!
)
GOTO END
:-h
:--help
:--?
:-?
type %~dp0\lib\help.txt
GOTO END
:--unzip
SET "a=%1"
IF /i NOT DEFINED a (
SET "MASK=\d+-[a-z]+.zip"
) ELSE if [%a%]==["all"] (
SET "MASK=\d+-[a-z]+.zip"
) else (
SET "MASK=!a!"
)
for /f "usebackq tokens=*" %%i in (`dir /a /b %dls%^|grep -iE "!MASK!"`) do (
#7z x %dls%\%%i -omypath\share\icons
)
GOTO :EOF
:END
#echo Done
ENDLOCAL
Update: I don't know if this will be helpful for anyone, but the problem was with SET /A paramCount=!index!. That let to always looking for a parameter with an argument no matter what, so that if my second parameter didn't have an argument, or neither did, it was causing problems. I 'solved' the problem by setting paramCount to !index!-1, but I think the upshot of this is that it's quite difficult to pass arbitrary, potentially optional, parameters to batch files, and probably should be parsed differently than I have done - or using a different coding language. That said, it's working fine now for what I need.

How can I run a command with a variable? /// How can I produce a random number with with a variable?

I'm aware that set zeroThroughNine=%Random%*9/32768 followed by echo %zeroThroughNine% will produce a a random number between and including 0 and 9. But it seems the interpreter doesn't evaluate the contents of the variable every time it is called, and as such, echo %zeroThroughNine% produces, for example, 7 every time.
I looked up a method for running commands using variables so that I could try to force it to work. I liked the question because it was very basal in its approach; something along the lines of "How can I run commands using variables?", tagged appropriately. I didn't much care for the answer because it was very narrow. The highest voted and selected answer was:
Simple. Just run set commandVar=echo "Hello world.", followed by echo %commandVar%.
Of course the truth is that only works for the echo command. >: [
Anyway I'll stop complaining. This is what I've tried:
set zeroThroughNine=set /a number=%Random%*9/32768 & echo %number% followed by echo %zeroThroughNine%
Unfortunately the & echo %number% section of my SET command runs immediately, producing "%number%" as output --and using echo %zeroThroughNine% produces "set /a number=8436*9/32768", for example, as output.
So two questions: How can I universally achieve running commands with the use of variables (or some alternative method), and perhaps more pressing, how can I achieve producing a new random number at the command line with each new command calling?
You should set number before you set zeroThroughNine to the command, like so:
set /a number=%Random%*9/32768
set zeroThroughNine=echo %number%
%zeroThroughNine%
Also, since zeroThroughNine already is an echo command, you don't need to add the extra echo before it.
EDIT:
Taking into account your Random calculation is needlessly complicated, the final code should be something like this (1 - 10 exclusive):
set /a number=%Random% %% 10
set zeroThroughNine=echo %number%
%zeroThroughNine%
Important thing is, rather than trying to do it all on one line, it is much more readable by separating it into two.
The CALL SET syntax allows a variable substring to be evaluated, the CALL page has more detail on this technique, in most cases a better approach is to use Setlocal EnableDelayedExpansion.
Command line (note that all % percent signs are escaped as ^% and that > and & characters are escaped within a pair of " double quotes:
set "zeroThroughNine=call set /a number=^%Random^% ^% 10>nul & call echo number=^%number^%"
%zeroThroughNine%
for /L %G in (1, 1, 10) do #%zeroThroughNine%
Batch script, CALL method (note that all % percent signs are escaped as %%):
#echo OFF
SETLOCAL
set "_zeroThroughNine=call set /a _number=%%Random%% %%%% 10 & call echo number=%%_number%%"
echo check variables
set _
echo output
%_zeroThroughNine%
for /L %%G in (1,1,10) do %_zeroThroughNine%
echo check variables after evaluating
set _
ENDLOCAL
Batch script, EnableDelayedExpansion only for output:
#echo OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "_zeroThroughNine=set /a _number=!Random! %% 10 & echo Number=!_number!"
SETLOCAL EnableDelayedExpansion
echo check variables
set _
echo output
%_zeroThroughNine%
for /L %%G in (1,1,10) do %_zeroThroughNine%
echo check variables after evaluating
set _
ENDLOCAL
ENDLOCAL
Batch script, EnableDelayedExpansion script wide (note that ! exclamation sign is escaped as ^!):
#echo OFF
SETLOCAL EnableExtensions EnableDelayedExpansion
set "_zeroThroughNine=set /a _number=^!Random^! %% 10 & echo NUMBER=^!_number^!"
echo check variables
set _
echo output
%_zeroThroughNine%
for /L %%G in (1,1,10) do %_zeroThroughNine%
echo check variables after evaluating
set _
ENDLOCAL
Check out this question. Basically, it's an issue with how the %random% environment variable works...
EDIT:
To elaborate, the reason your random value was always 7 is because of how cmd's pseudo-random number generator works, not because of how the variables are interpreted. The matter is explained very well in this answer.
Essentially, in repeated runs of a batch file, %RANDOM% will produce a value very close to the previous run. Thus, the expression %RANDOM%*9/32768 produces the same result in each separate run because of the random value.
If I understand correctly, the question you're asking is how to better generate a random value 0 - 9 inclusive, which would be by using the following expression:
set /a zeroThroughNine=%RANDOM% %% 10

Windows Batch Function Removing Special Character From Arguments

I am creating a Windows batch file with a function that takes a variable number of arguments. When I call this batch file the string that I am passing to it contains the special character %.
Prior to calling this function I set a variable containing the string (which contains the special character) and call the function as follows:
SETLOCAL ENABLEDELAYEDEXPANSION
...
SET "MYSTRING=Hello "%%V""
ECHO My String=!MYSTRING!
CALL :BATCH_FUNCTION !MYSTRING!
GOTO :EOF
Here is my batch function definition:
:BATCH_FUNCTION
SET "ARGS=%*"
ECHO Arguments=!ARGS!
GOTO :EOF
The problem I am running into is the % sign is getting stripped out inside the function.
Here are the outputs to demonstrate:
My String=Hello "%V"
Arguments=Hello "V"
Can anyone tell me how to setup either the function arguments or the function itself to not strip off the % sign?
Thank you.
The problem is that CALL has an extra round of parsing that consumes the %. See phase 6 in How does the Windows Command Interpreter (CMD.EXE) parse scripts? for more information.
One solution that requires minimal change to your code is to use variable expansion search and replace to double up the percents in your CALL statement. The delayed expansion search and replace occurs in phase 5, and then phase 6 consumes the extra percent to get back to the original value. Note that the percents must be doubled in the search and replace expression because phase 1 percent consumption occurs before the delayed expansion in phase 5.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET "MYSTRING=Hello "%%V""
ECHO My String=!MYSTRING!
CALL :BATCH_FUNCTION !MYSTRING:%%=%%%%!
GOTO :EOF
:BATCH_FUNCTION
SET "ARGS=%*"
ECHO Arguments=!ARGS!
GOTO :EOF
But that is not the best way to pass string values to functions. There are other characters that cause additional complications. And there is no good solution for passing a quoted ^ literal in a CALL statement.
The best way to pass any string value to a function is to pass by reference, meaning pass the name of a variable, and then use delayed expansion to access the value of the variable. However, this does not help with the %* construct.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
SET "MYSTRING=Hello "%%V""
ECHO My String=!MYSTRING!
CALL :BATCH_FUNCTION MYSTRING
GOTO :EOF
:BATCH_FUNCTION
SET "ARG1=!%1!"
ECHO ARG1=!ARG1!
GOTO :EOF
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "MYSTRING=Hello "%%V""
ECHO My String=!MYSTRING!
CALL :BATCH_FUNCTION !MYSTRING:%%=%%%%!
CALL :BATCH_FUNCTION2 MYSTRING
GOTO :EOF
:BATCH_FUNCTION
SET "ARGS=%*"
ECHO Arguments=!ARGS!
GOTO :EOF
:BATCH_FUNCTION2
FOR /f "tokens=1*delims==" %%a IN ('set %1') DO IF /i "%1"=="%%a" ECHO Arguments=%%b
GOTO :EOF
Here's two different ways. Neither is bullet-proof. Oftentimes, a batch solution processing strings has holes.

Find and Replace inside for loop [batch script]

The below code works, echo test.test
set replaceWith=.
set str="test\test"
call set str=%%str:\=%replaceWith%%%
echo %str%
But, the below code echo ggg.hhhhh all the 4 times.
SET SERVICE_LIST=(aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh)
for %%i in %SERVICE_LIST% do (
set replaceWith=.
set str="%%i"
call set str=%%str:\=%replaceWith%%%
echo %str%
)
What am I doing wrong here?
If you understand why your code uses call set str=%%str:\=%replaceWith%%%, then you should be able to figure this out ;-)
Syntax like %var% is expanded when the line is parsed, and your entire parenthesized FOR loop is parsed in one pass. So %replaceWith% and echo %str% will use the values that existed before you entered your loop.
The CALL statement goes through an extra level of parsing for each iteration, but that only partially solves the issue.
The first time you ran the script, you probably just got "ECHO is on." (or off) 4 times. However, the value of str was probably ggghhhhh and replaceWith was . after the script finished. You don't have SETLOCAL, so when you run again, the values are now set before the loop starts. After the second time you run you probably got ggghhhhh 4 times. And then from then on, every time you run the script you get ggg.hhhhh 4 times.
You could get your desired result by using CALL with your ECHO statement, and moving the assignment of replaceWith before the loop.
#echo off
setlocal
SET SERVICE_LIST=(aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh)
set "replaceWith=."
for %%i in %SERVICE_LIST% do (
set str="%%i"
call set str=%%str:\=%replaceWith%%%
call echo %%str%%
)
But there is a better way - delayed expansion
#echo off
setlocal enableDelayedExpansion
SET "SERVICE_LIST=aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh"
set "replaceWith=."
for %%i in (%SERVICE_LIST%) do (
set str="%%i"
set str=!str:\=%replaceWith%!
echo !str!
)
Please have a text book for Windows Command Shell Script Language and try this:
#ECHO OFF &SETLOCAL
SET "SERVICE_LIST=(aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh)"
for /f "delims=" %%i in ("%SERVICE_LIST%") do (
set "replaceWith=."
set "str=%%i"
SETLOCAL ENABLEDELAYEDEXPANSION
call set "str=%%str:\=!replaceWith!%%"
echo !str!
ENDLOCAL
)

Variable not getting set correctly in a dos batch file on XP

I am trying to write a batch file to be run in a command prompt on XP. I am trying to get a listing of files in a specific path that follow a certain naming convention. I need to copy and rename each file instance to a static name and drop it to a transmission folder.
Since it may take a little while for the file to go in the transmission folder, I need to check before I copy the next file over so that I don't overlay the previous file. I am not able to use SLEEP or TIMEOUT since I don't have the extra toolkit installed. I try to just continually loop back to a START section until the file is sent.
I noticed that if I passed the %%x value set in the for loop that if I loop back to the START section a couple of times, it seems to lose its value and it is set to nothing. So I tried to set a variable to hold the value.
I seem to be having issues with the variable not being set correctly or not cleared. Originally it kept on referencing the first file but now it doesn't seem to be set at all. The ECHO displays the correct the value but the filename variable is empty still.
Does anyone know a better way of doing this? Thanks in advance for your help as I have already wasted a whole day on this!
This is the batch file:
#ECHO "At the start of the loop"
#for %%x in (C:\OUTBOUND\customer_file*) do (
#ECHO "In the loop"
#ECHO "loop value ="
#ECHO %%x
SET filename=%%x
#ECHO "filename ="
#ECHO %filename%
#ECHO ...ARCHIVE OUTBOUND CUSTOMER FILE
archivedatafile --sourcefile="%filename%" --archivefolder="..\archivedata\customer" --retentiondays=0
IF NOT %ERRORLEVEL%==0 GOTO ERROR
PAUSE
:START
IF EXIST l:\OutputFile (
#ping 1.1.1.1 -n 1 -w 30000
GOTO START
) ELSE (
COPY %filename% l:\OutputFile /Y
IF NOT %ERRORLEVEL%==0 GOTO ERROR
PAUSE
)
)
GOTO END
:ERROR
#echo off
#ECHO *************************************************************
#ECHO * !!ERROR!! *
#ECHO *************************************************************
:END
SET filename=
foxidrive has provided a script that should work, but did not provide an explanation as to why your code fails and how he fixed the problems.
You have 2 problems:
1) Your FOR loop is aborted immediately whenever GOTO is executed within you loop. It does not matter where the GOTO target label is placed - GOTO always terminates a loop. Foxidrive's use of CALL works perfectly - the loop will continue once the CALLed routine returns.
2) You attempt to set a variable within a block of code and then reference the new value within the same block. %VAR% is expanded when the statement is parsed, and complicated commands like IF and FOR are parsed once in their entirety in one pass. Actually, any block of code within parentheses is parsed in one pass. So the values of %ERRORLEVEL% and %FILENAME% will be constant - the values that existed before the block was entered.
As Endoro has indicated, one way to solve that problem is to use delayed expansion. Delayed expansion must be enabled by using setlocal enableDelayedExpansion, and then expand the variable using !VAR!. The value is expanded at execution time instead of parse time. Type HELP SET from the command prompt for more information about delayed expansion.
But beware that delayed expansion can cause its own problems when used with a FOR loop because the delayed expansion occurs after the FOR variable expansion: %%x will be corrupted if the value contains a !. This problem can be solved by carefully toggling delayed expansion ON and OFF as needed via SETLOCAL and ENDLOCAL.
Foxidrive's code avoids the entire delayed expansion issue by using CALL. His :NEXT routine is not inside a FOR loop, so all the commands are reparsed each time it is called, so delayed expansion is not required.
This may work - it is untested:
#echo off
ECHO Starting...
for %%x in (C:\OUTBOUND\customer_file*) do call :next "%%x"
echo done
pause
goto :eof
:next
ECHO ...ARCHIVING OUTBOUND CUSTOMER FILE "%~1"
archivedatafile --sourcefile="%~1" --archivefolder="..\archivedata\customer" --retentiondays=0
IF ERRORLEVEL 1 GOTO :ERROR
:loop
echo waiting for file...
ping -n 6 localhost >nul
IF EXIST l:\OutputFile GOTO :loop
COPY "%~1" l:\OutputFile /Y
IF ERRORLEVEL 1 GOTO :ERROR
GOTO :EOF
:ERROR
ECHO *************************************************************
ECHO * !!ERROR!! in "%%x"
ECHO *************************************************************
pause
goto :EOF
try this:
#echo off&setlocal
for %%x in (C:\OUTBOUND\customer_file*) do SET "filename=%%x"
ECHO %filename%
ECHO ...ARCHIVE OUTBOUND CUSTOMER FILE
archivedatafile --sourcefile="%filename%" --archivefolder="..\archivedata\customer" --retentiondays=0
IF NOT %ERRORLEVEL%==0 GOTO:ERROR
PAUSE
:START
IF EXIST l:\OutputFile ping 1.1.1.1 -n 1 -w 30000&GOTO:START
COPY "%filename%" l:\OutputFile /Y
IF NOT %ERRORLEVEL%==0 GOTO:ERROR
PAUSE
GOTO:END
:ERROR
echo off
ECHO *************************************************************
ECHO * !!ERROR!! *
ECHO *************************************************************
:END
SET "filename="
If you use codeblocks (if and for with ( )) and variables with changing values you have to enable delayed expansion. You don't need code blocks in this code, as you can see.

Resources