Flow control in a batch file - for-loop

Reference Iterating arrays in a batch file
I have the following:
for /f "tokens=1" %%Q in ('query termserver') do (
if not ERRORLEVEL (
echo Checking %%Q
for /f "tokens=1" %%U in ('query user %UserID% /server:%%Q') do (echo %%Q)
)
)
When running query termserver from the command line, the first two lines are:
Known
-------------------------
...followed by the list of terminal servers. However, I do not want to include these as part of the query user command. Also, there are about 4 servers I do not wish to include. When I supply UserID with this code, the program is promptly exiting. I know it has something to do with the if statement. Is this not possible to nest flow control inside the for-loop?
I had tried setting a variable to exactly the names of the servers I wanted to check, but the iteration would end on the first server:
set TermServers=Server1.Server2.Server3.Server7.Server8.Server10
for /f "tokens=2 delims=.=" %%Q in ('set TermServers') do (
echo Checking %%Q
for /f "tokens=1" %%U in ('query user %UserID% /server:%%Q') do (echo %%Q)
)
I would prefer this second example over the first if nothing else for cleanliness.
Any help regarding either of these issues would be greatly appreciated.

Again, there are multiple things to note here.
if errorlevel
The help for if says:
IF [NOT] ERRORLEVEL number command
as syntax for the if errorlevel condition. That is, you must provide a number to compare against. Keep in mind that if errorlevel n evaluates to true if the exit code was at least n.
So
if errorlevel 1 ...
catches any error (that is signaled through the exit code), while
if errorlevel 0 ...
simply is always true.
Anyways, you probably want a
if not errorlevel 1 ...
here, since that condition is true if no error occurred.
Skipping lines
The for /f command has an argument skip=n which can be used to skip lines at the start. If your output starts with two lines you don't want, then you can just do
for /f "skip=2 tokens=1" %%Q in ('query termserver') do
Iterating over multiple known values in for /f
The problem with your second code snippet is that for iterates line-wise. So when you give it a single environment variable it will tokenize it (and put the tokens into different variables), but the loop runs only once per line. Also note that using set here is a bit error-prone as you might get more back than you want. Something like
for /f ... in ("%TermServers%") ...
would have been easier. Still, that doesn't solve the original problem. The easiest way to solve this would probably be something like the following:
rem space-separated list of servers
set TermServers=Server1 Server2 Server3 Server7 Server8 Server10
rem call the subroutine with the list of servers
call :query_servers %TermServers%
rem exit the batch file here, to prevent the subroutine from running again afterwards
goto :eof
rem Subroutine to iterate over the list of servers
:query_servers
rem Process the next server in the list
rem Note the usage of %1 here instead of a for loop variable
echo Checking %1
for /f "tokens=1" %%U in ('query user %UserID% /server:%1') do (echo %%Q)
rem Remove the first argument we just processed
shift
rem if there is still another server to be processed, then do so
rem we're mis-using the subroutine label as a jump target here too
if not [%1]==[] goto query_servers
rem This is kind of a "return" statement for subroutines
goto :eof
(untested, but should work.)
ETA: Gah, and once again I miss the most obvious answer:
set TermServers=Server1 Server2 Server3 Server7 Server8 Server10
for %%S in (%TermServers%) do (
for /f "tokens=1" %%U in ('query user %UserID% /server:%1') do (echo %%Q)
)
Note that this is simply for, not for /f and it will dutifully iterate over a list of values. I don't know how I missed that one, sorry.

NT shell/batch language is not smart enough to accept IF NOT ERRORLEVEL (... -- you need to do an explicit comparison, like this:
if not %ERRORLEVEL%==0 (
...

Related

Extract first character from a string

I've no knowledge regarding Windows batch programming syntax. I have a text file containing user IDs which I need to delete using curl command and for that I need to extract first character of every user ID and then pass to the curl command. I know the curl command which will require two variables:
'UserID' - Read from the text file.
'firstCharacter' - Extracting first character from the User ID.
Below is the code to fetch user IDs from users.txt file:
#echo off
for /f "tokens=*" %%a in (users.txt) do call :processline %%a
pause
goto :eof
:processline
echo %*
goto :eof
:eof
Please help me with extracting the first character from the read User IDs.
Thanks.
The cmd.exe can do a limited amount of string parsing. JosefZ gave you a good place to start.
C:>echo %PROCESSOR_ARCHITECTURE%
AMD64
C:>echo %PROCESSOR_ARCHITECTURE:~0,1%
A
In a batch / command file I was always needing the first x characters or a string and ended up with many functions all doing the same thing but different names, i.e. getFirstChar, getFirstTwoChars, etc.. - so decided to make a generic function where I could pass in the number of characters I needed:
::-- getFirstXChars
:getFirstXChars
set sValIn=%1
set /a iNo=%2
set vWorkVal=%%sValIn:~0,%iNo%%%
call:getFirstX %vWorkVal% vWorkVal2
set %3=%vWorkVal2%
:getFirstX
set %2=%~1
goto:eof
to use the syntax would be
set varToTrim=ABCDEFG
call:getFirstXChars %varToTrim% 2 varToTrimAfter
#echo 1 %varToTrim%
#echo 2 %varToTrimAfter%
pause
result from command file:
1 ABCDEFG
2 AB
I just added to set variable "id" to be %%a, then I used substring notation to get the first character.
Substrings are processed by using :~start,length after the variable name and before the last % in the variable.
#echo off
for /f "tokens=*" %%a in (output.txt) do set id=%%a & call :processline %%a
pause
goto :eof
:processline
echo %id:~0,1%
goto :eof
:eof

Windows batch: REN does not halt even if errorlevel 1

I'm using REN to find files with a certain naming pattern and modify them, like so:
REN "?%var1%?%var2%.S16" "?%var1%?%var3%.S16"
This finds all files like aXaY.S16, bXaY.S16, cXbY.S16 (etc) and renames them to aXaZ.S16, bXaZ.S16, cXbZ.S16 (etc). If it finds what it's looking for, it works just fine. But there's a problem: REN won't halt the operation if it encounters an error.
To prove this is the case, my script is as follows:
#echo off
set /p var1=Enter first var:
set /p var2=Enter second var:
set /p var3=Change second var to:
echo Searching for all files matching ?%var1%?%var2%.S16
REN "?%var1%?%var2%.S16" "?%var1%?%var3%.S16"
echo Errorlevel: %errorlevel%
IF ERRORLEVEL 1 goto :FAIL
echo Success!
PAUSE
goto :eof
:FAIL
echo I done goofed!
PAUSE
exit
I ran this in a folder containing a few hundred files. I searched for files matching ?0?a.S16 (of which there are ~40 results) and asked it to change the 'a' to a 'c', knowing that files with this name already exist and should create a conflict.
Here is the console output (shortened for brevity):
Enter first var: 0
Enter second var:a
Change second var to:c
Searching for all files matching ?0?a.S16
A duplicate file name exists, or the file
cannot be found.
A duplicate file name exists, or the file
cannot be found.
A duplicate file name exists, or the file
cannot be found.
...(etc)...
Errorlevel: 1
I done goofed!
Press any key to continue . . .
The complaint about duplicates goes on for about 40 lines, as expected. As you can see, though, Errorlevel returns 1 at the end of the process instead of quitting at the first sign of trouble, which is what I'd rather it do.
I did consider passing this through FOR /f but I don't know how I would also pass the searchmask through it. I know FOR supports '*' wildcards, but as far as I'm aware, it doesn't support '?' the same way REN does. A possible alternative would be to use regular expressions somehow, but I can't wrap my head around them at all despite trying.
Any clues? Many thanks for taking a look.
Yes, you have documented how the REN command works - it continues to completion, even after a rename fails, and then reports ERRORLEVEL 1 if at least one rename failed.
If you want to halt processing upon the first error, then you will have to write your own loop to rename each file, one at a time. You should not use the simple FOR loop because it can begin iterating before it has scanned the entire directory, so you run the risk of renaming the same file twice. The safe thing to do is use FOR /F coupled with DIR /B /A-D instead.
#echo off
set /p var1=Enter first var:
set /p var2=Enter second var:
set /p var3=Change second var to:
echo Searching for all files matching ?%var1%?%var2%.S16
for /f "eol=: delims=" %%F in (
'dir /b /a-d "?%var1%?%var2%.S16"'
) do ren "%%F" "?%var1%?%var3%.S16" || goto :break
:break
echo Errorlevel: %errorlevel%
IF ERRORLEVEL 1 goto :FAIL
echo Success!
PAUSE
goto :eof
:FAIL
echo Errorlevel: %errorlevel%
echo I done goofed!
PAUSE
exit
Note - The wildcard rules used by REN are not at all intuitive. You should have a look at How does the Windows RENAME command interpret wildcards? to make sure you are getting the results you expect.
replace you ren command with
FOR /f "delims=" %%x IN ('dir /b /a-d "%sourcedir%\?%var1%?%var2%.S16" 2^>nul') DO REN "%sourcedir%\%%x" "?%var1%?%var3%.S16"&IF ERRORLEVEL 1 ECHO failed AT %%x&goto :EOF
note This is a direct patch of my test, where I set sourcedir to a testing directory. In your case, you'd need to make appropriate adjustments.
This makes a directory list in basic form without directories and each filename is then assigned to %%x Then rename is then attempted and the resultant errorlevel interpreted. On fail, show the name for good measure and bail out.
[edit] 2^>nul added into dir command.
This will cause dir errors (like "file not found") to be directed to nowhere. The caret (^) is required to tell cmd that the > is part of the dir command, not the for.

Batch ECHO %varname% just saying "Echo is on."

So I was tasked with making a batch file that does a few specific things. I've never worked with batch before, and I'm finding it hard to find tutorials on what exactly I need. (I've done basic tutorials)
I'm trying to get the most currently edited file from a directory. The only thing I've came up with (and I've noticed other people said to do) is a for loop of files in the directory sorted by date and then just get the first file and break the loop.
Some problems:
1) My loop never breaks
2) My ECHO %variable% doesn't work at the end.
#echo off
SET count=0
FOR /f %%i in ('DIR Y:\ /B /O:-D') DO (
IF count==0 (
SET NewestFile=%%i
SET count=1
)
)
#echo on
ECHO %NewestFile%
When I run this, I get:
C:\>testing.bat
C:\>ECHO
ECHO is on.
I am 100% new to Batch. Maybe I'm doing something that this is really picky about? (Other StackOverflow questions have been solved by people just adding aa space or stuff like that)
Your condition is never met because the string count is never equal to the string 0. You need
if !count!==0 (
set NewestFile=%%i
set count=1
)
But then you also need delayed expansion (at the beginning of your batch file):
setlocal enabledelayedexpansion
The problem here is that you need to tell the batch file that there is a variable. Like foo in Perl won't magically resolve to the contents of the $foo variable count in your batch file isn't equivalent to %count% (the variable contents) or !count! (the same, but with delayed expansion).
Delayed expansion is necessary because the whole for loop is parsed at once. And cmd replaces normal (%foo%) variables with their contents during parsing so that during execution only the values remain. So once execution reaches the if there would be the condition 0==0 because that's what count's value was before the loop. Delayed expansion (using the !foo! syntax) expands the variables immediately prior to execution, so this does not happen.
For more help on delayed expansion you can read help set.
Another way would be to just use absence or presence of the count variable:
SET count=
FOR /f %%i in ('DIR Y:\ /B /O:-D') DO (
IF not defined count (
SET NewestFile=%%i
SET count=1
)
)
This works around the problem above because there is no variable to replace during parsing. All we're doing is a run-time check whether the variable count exists.
If you supplied accurate code then you want to get the first line - and this is one way to do that.
#echo off
FOR /f %%i in ('DIR Y:\ /B /O:-D') DO SET "NewestFile=%%i" & goto :done
:done
ECHO %NewestFile%
If you change the dir command to list the files in ascending order instead of descending order, you can use this one-liner which doesn't need any of the common bizarre cmd.exe scripting hacks. It just keeps the last line of output in the NewestFile variable (I guess it might qualify as a cmd.exe scripting hack, but I don't think it qualifies as bizarre):
for /f %%i in ('DIR Y:\ /B /O:D') do set NewestFile=%%i

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.

Batch: compare files using CRC32

I have given myself a simple task of writing a batch file that will do the following:
Compare CRC32 of each file in "first" folder with each file in "second" folder. Output needs to be the name of every file from the "first" folder, which doesn't have its duplicate in "second".
Note: "CRC32.exe -nf" outputs CRC32 in first line, and file size in second one.
Here is how I tried to do it:
#echo off
for %%i in ("%cd%\first\*.*") do (
set uniq=0
for /f %%x in ('crc32 %%i -nf') do (
set one=%%x
goto find_two
)
:find_two
for %%j in ("%cd%\second\*.*") do (
for /f %%x in ('crc32 %%j -nf') do (
set two=%%x
goto compare
)
:compare
if one==two (
goto next
) else (
set uniq=1
)
)
if uniq==1 (
echo %%i >>result.txt
)
:next
)
I assume there are several errors present in this code, but I had trouble finding them. So if anyone has time, and thinks he can help, I would be grateful.
If you think different approach is required, feel free to show it.
There are two major problems.
Goto's inside brackt doesn't work as expected, they stop the loop immediately, so your code will simply fail there.
The solution is to avoid goto or to call a function and use the goto there.
The second problem are the compares.
if one==two ( will be always false, as it compare the word one against the word two, but not the content of the variables.
Normally you would use if "%one%"=="%two%" echo equal, but as it should work inside of brackets you need delayed expansion, so it looks like.
if !one!==!two! echo equal
Don't forget to enable the delayed expansion before you start the loop with
setlocal EnableDelayedExpansion
Might be easier to capture the output of crc32 using something like this:
for /f %%x in ('crc32 "%filename%" -nf ^| find /v "CRC32"') do stuff with %%x
Can you show me the full output of an example crc32 if this is not helpful?

Resources