Why does for /f not ignore blank lines? - windows

I'm trying to get a simple value from a for /f loop in a batch file.
Using the command wmic path win32_localtime get dayofweek gives me the output:
C:\sendemail>wmic path win32_localtime get dayofweek
DayOfWeek
1
C:\sendemail>
So, using the following batch script, I should be able to return a result of "1":
set cmd=wmic path win32_localtime get dayofweek
for /f "tokens=1 skip=1" %%Z in ('%cmd%') do set _myday=%%Z
echo Var is %_myday%
But I don't, it sets the variable at least twice, as seen here :
C:\sendemail>set cmd=wmic path win32_localtime get dayofweek
C:\sendemail>for /F "tokens=1 skip=1" %Z in ('wmic path win32_localtime get dayofweek') do set _myday=%Z
C:\sendemail>set _myday=1
:\sendemail>set _myday=
C:\sendemail>echo Var is
Var is
C:\sendemail>
At first, I wondered why, then I realised the loop is processing the two blank lines... which it shouldn't be. according to this: http://ss64.com/nt/for_cmd.html
Skip SKIP will skip processing a number of lines from the beginning of
the file. SKIP includes empty lines, but after the SKIP is complete,
FOR /F ignores (does not iterate) empty lines.
Either, it's not working normally, or those lines are not blank, and are filled with something...
At any rate, how do I get just the day of the week result out of this, and ignore the rest?

About the lines contents, yes, the output from wmic includes at the end of each line an additional carriage return character that is detected by the for command. That is the reason for your non empty lines.
You can try with
for /f "tokens=2 delims==" %%Z in ('
wmic path win32_localtime get dayofweek /value
') do for /f %%Y in ("%%Z") do set "_myday=%%Y"
The code is asking wmic to return the information in key=value format and using the equal sign to tokenize the input records. As we request only the second token, for will only process lines that return at least two tokens, lines having a key (first token), an equal sign (delimiter) and a value (second token)
But, the additional carriage return at the end of the line is also included in the value of the variable.
When the variable is used with normal syntax (%_myday%) it is not seen, but with delayed expansion (!_myday!) the additional carriage return will be echoed.
To remove this additional character from the variable, a second for command has been used in the posted code.

This slight midification will work:
#ECHO OFF
set cmd=wmic path win32_localtime get dayofweek
for /f "tokens=1 skip=1" %%Z in ('%cmd%') do (
set _myday=%%Z
GOTO BREAK
)
:BREAK
echo Var is %_myday%
PAUSE
Simply jump out of the loop after reading the desired line.

The above is a good approach and intitially I applied it successfully. Because of another need to capture a WMI variable and load into an environment variable, I wanted some that would be a native command line. I stumbled on this...
for /F "skip=2 tokens=2 delims=," %%i IN ('wmic timezone get Bias /format:csv') do (echo var Bias=%%i; > %TZONEDB%mid.htm )
The core is that using the CSV format makes skip work (no trailing blanks) and gives you easily parsed output.
ed g

I recently had the issue discussed here and whilst previous answers helped I wanted to retrieve several variables for subsequent testing. At first I used code similar to the following example (just not on explorer.exe and with different variable names. The following variable names have been deliberately chosen to match variable name restrictions in the later code).
setlocal
for /F "skip=2 tokens=2,3,4 delims=," %%a in ('
wmic /node:"MyFileServer-pc" process where Name^="explorer.exe"
get HandleCount^,
ParentProcessId^,
ThreadCount
/format:csv
') do (
set ProcHandleCount=%%a
set ProcParentProcessId=%%b
set ProcThreadCount=%%c
)
echo HandleCount %ProcHandleCount%, ParentProcessID %ProcParentProcessId%, ThreadCount %ProcThreadCount%
However, I dislike this solution as wmic always returns results in the order in which it internally iterates them disregarding the order in which they are presented to the wmic command, So the command wmic process where Name^="explorer.exe" get ThreadCount^,HandleCount^,ParentProcessId still returns ThreadCount last. This makes the previous code error prone when fetching multiple parameter value pairs and a hassle to get the ordering correct.
So instead I used the following code, which achieves the same result avoiding the need to care about the ordering of wmic parameters. It is also very easy to add additional parameters still with no need to care about ordering.
setlocal
for /F "tokens=1,2delims==" %%a in ('
wmic /node:"MyFileServer-pc" process where Name^="explorer.exe"
get HandleCount^,
ParentProcessId^,
ThreadCount
/value
') do #set Proc%%a^=%%b
echo HandleCount %ProcHandleCount%, ParentProcessID %ProcParentProcessId%, ThreadCount %ProcThreadCount%
In the above code, the variable naming is provided by the parameter of each returned parameter=value pair from wmic plus a fixed prefix, automatically matching returned parameter and value pairs. Note the use of a prefix, in this case 'Proc', without at least a one character prefix returned wmic blank lines cause errors. If different environment variable names are needed then the first solution allows freedom in naming and allows for shorter names but is more error prone when multiple values are required.
For what little it's worth... the following code avoids the variable prefixes and silences errors. More unique variable names would usually be desired.
(
for /F "tokens=1,2delims==" %%a in ('
wmic /node:"MyFileServer-pc" process where Name^="explorer.exe" get HandleCount^,ParentProcessId^,ThreadCount /value
') do #set %%a=%%b
) 2>nul
These examples use the wmic parameter /node:"MyFileServer-pc" to get remote process info but work equally for simpler wmic commands.

I always use this batch template when processing wmic output:
setlocal enableextensions enabledelayedexpansion
for /f "delims and such" %%a in ('wmic path win32_localtime get dayofweek 2^>nul ^<nul') do (
set VAR=%%a
CALL :RMCRLF VAR
if "!VAR!" NEQ "" (
rem If we get here, then it's a non-empty line
rem ... Do something ...
)
)
GOTO :EOF
:RMCRLF
:: All this does is clean stray carriage returns/line feeds off of a variable,
:: which wmic is notorious for injecting into its output.
set RMCRVAR=%1
IF NOT DEFINED RMCRVAR GOTO :EOF
CALL set %RMCRVAR%=%%%RMCRVAR%%%
GOTO :EOF
They key here is the "function" (if you will) :RMCRLF. It will strip the pesky carriage returns off of a variable so you can check for true emptiness of the string, and then decide to skip the empty line.

Related

FINDSTR to find text START END of string

I have string photo="999" price="10" category="1" . I want to get only 10. This means I need to the string which start price=" and ends with "
#For /F "Tokens=1*Delims==" %%A In ('FindStr /I "^price=" "C:\price.txt" 2^>NUL')Do #Set "Ver=%%~B"
#Echo(%%Ver%% = %Ver% & Pause
findstr always returns the complete line, if successful. So it's not the right tool for this task (actually, there is no tool in cmd at all that could do that this way).
But with a bit of logic, you can work around it: remove the part from the start until (including) the triggerword price (a task, the set command is happy to do), then process the rest with a for /f loop to get the desired substring:
set "string=photo="999" price="10" category="1""
echo check: %string%
echo debug: %string:*price=%
for /f tokens^=2^ delims^=^" %%a in ("%string:*price=%") do set "ver=%%~a"
echo ver=%ver%
If you are sure of the exact format of your string (in your example the searched substring is the second quoted argument, so the fourth token when splitted by ") it gets as easy as:
for /f tokens^=4^ delims^=^" %%a in ("%string%") do echo ver=%%~a
or
for /f tokens^=4^ delims^=^" %%a in (file.txt) do echo ver=%%~a
#ECHO OFF
SETLOCAL
set "string=photo="999" price="10" category="1""
:: remove quotes
set "string=%string:"=%"
for /f %%a in ("%string:* price=%") do set /a pricefound%%a
set pri
goto :eof
Since we don't have a representative sample of the file in question, we're forced to the conclusion that the requirement is to find the one and only appearance of price="anumber" in the file.
So, since findstr output, properly framed, would select this line, all we need do is process the string.
This is kind of a quick-and-dirty method; it may be adequate for OP's purpose.
First, remove the quotes from the string as they have a habit of interfering.
Next, use for /f in string-processing mode where it does its magic on the quoted string in parentheses. The string is the original string, minus quotes, so replace all characters up to "Spaceprice" with nothing and take the first token of the result, resulting in =10 assigned to %%a in the example case.
Then execute "set /a somevariablename=10" by simply concatenating the two strings.
Note that if the file contains a line like ... pricelastweek="9" ... then other measures may need to be taken.
Here's an example which tries to follow a similar methodology as your example code.
It uses FindStr to isolate any line in C:\price.txt, which includes the word price="<OneOrMoreDigits>". That line is saved as a variable named price, which is split under delayed expansion in a nested For loop, to remove everything up to, and including the first instance of the string price, leaving, in this case, ="10" category="1". The nested loop further splits that, to take the second token, using a doublequote character as the delimiter, (which should be your required value).
#For /F Delims^=^ EOL^= %%G In ('%__AppDir__%findstr.exe /IR "\<price=\"[0123456789]*\"\>" "C:\price.txt"') Do #(Set "price=%%G" & SetLocal EnableDelayedExpansion
For /F Tokens^=2^ Delims^=^" %%H In ("!price:* price=!") Do #EndLocal & Set "price=%%H")
#Echo %%price%% = %price% & Pause
Well clearly you need to match lines that contain price=" as there may be other lines.
What's unclear is if you need match 10 exactly, or just want that to be any number.
It seems likely you just want to match any number and grab it.
This is done easily with:
#For /F "Tokens=4 Delims=^= " %%A In ('
TYPE "C:\price.txt" ^| FIND /I "price="""') Do #(
Set "Ver=%%~A" & CALL SET Ver &Pause )
While is you need to match Price="10", which seems less useful, but at least one person took that meaning and your wording is a little unclear so I will add that was well:
#For /F "Tokens=4 Delims=^= " %%A In ('
TYPE "C:\price.txt" ^| FIND /I "price=""10"""') Do #(
Set "Ver=%%~A" & CALL SET Ver &Pause )
Note in all examples I left in the # symbols since I assume this is you being clever, and leaving ECHO ON and only removing the # symbols when you want to debug some specific thing you are doing.
However, in case not, it's worth pointing out that in a script it's usually easiest to place ECHO OFF at the start of the script instead of putting an # at the beginning of each statement to stop it from echoing.
Cheers! :)

Windows Batch file - strip leading characters

I have a batch file which copies some local files up to a google storage area using the gsutil tool. The gsutil tool produces a nice log file showing the details of the files that were uploaded and if it was OK or not.
Source,Destination,Start,End,Md5,UploadId,Source Size,Bytes Transferred,Result,Description
file://C:\TEMP\file_1.xlsx,gs://app1/backups/file_1.xlsx,2018-12-04T15:25:48.428000Z,2018-12-04T15:25:48.804000Z,CPHHZfdlt6AePAPz6JO2KQ==,,18753,18753,OK,
file://C:\TEMP\file_2.xlsx,gs://app1/backups/file_2.xlsx,2018-12-04T15:25:48.428000Z,2018-12-04T15:25:48.813000Z,aTKCOQSPVwDycM9+NGO28Q==,,18753,18753,OK,
What I would like to do is to
check the status result in column 8 (OK or FAIL)
If the status is OK then move the source file to another folder (so that it is not uploaded again).
The problem is that the source filename is appended with "file://" which I can't seem to remove, example
file://C:\TEMP\file_1.xlsx
needs to be changed into this
C:\TEMP\file_1.xlsx
I am using a for /f loop and I am not sure if the manipulation of the variables %%A is different within a for /f loop.
#echo off
rem copy the gsutil log file into a temp file and remove the header row using the 'more' command.
more +1 raw_results.log > .\upload_results.log
rem get the source file name (column 1) and the upload result (OK) from column 8
for /f "tokens=1,8 delims=," %%A in (.\upload_results.log) do (
echo The source file is %%A , the upload status was %%B
set line=%%A
set line=!line:file://:=! >> output2.txt echo !line!
echo !line!
)
The output is like this.
The source file is file://C:\TEMP\file_1.xlsx , the upload status was OK
The source file is file://C:\TEMP\file_2.xlsx , the upload status was OK
I'm expecting it to dump the altered values out into a new file but it is not producing anything at the moment.
Normally I would extract from a specific character to the end of the string with something like this but it doesn't work with my For/f loop.
%var:~7%
Any pointers or a different way of doing it greatly appreciated.
Since the part to remove seems fixed it is easier to use substrings.
Also using for /f "skip=1" evades he neccessity of the external command more +1 and another intermediate file.
#echo off & setlocal EnableDelayedExpansion
type NUL>output2.txt
for /f "skip=1 eol=| tokens=1,8 delims=," %%A in (.\upload_results.log) do (
echo The source file is %%A , the upload status was %%B
set "line=%%A"
set "line=!line:~7!"
echo(!line!>>output2.txt
echo(!line!
)
File names and paths can contain also one or more exclamation marks. The line set line=%%A is parsed by Windows command processor a second time before execution with enabled delayed expansion. See How does the Windows Command Interpreter (CMD.EXE) parse scripts? Every ! inside the string assigned to loop variable A is on this line interpreted as begin or end of a delayed expanded environment variable reference. So the string of loop variable A is assigned to environment variable line with an unwanted modification if file path/name contains one or more exclamation marks.
For that reason it is best to avoid usage of delayed expansion. The fastest solution is for this task using a second FOR to get file:// removed from string assigned to loop variable A.
#echo off
del output2.txt 2>nul
for /F "skip=1 tokens=1,8 delims=," %%A in (upload_results.log) do (
echo The source file is %%A , the upload status was %%B.
for /F "tokens=1* delims=/" %%C in ("%%~A") do echo %%D>>output2.txt
)
Even faster would be without the first echo command line inside the loop:
#echo off
(for /F "skip=1 delims=," %%A in (upload_results.log) do (
for /F "tokens=1* delims=/" %%B in ("%%~A") do echo %%C
))>output2.txt
The second solution can be written also as single command line:
#(for /F "skip=1 delims=," %%A in (upload_results.log) do #for /F "tokens=1* delims=/" %%B in ("%%~A") do #echo %%C)>output2.txt
All solutions do following:
The outer FOR processes ANSI (fixed one byte per character) or UTF-8 (one to four bytes per character) encoded text file upload_results.log line by line with skipping the first line and ignoring always empty lines and lines starting with a semicolon which do not occur here.
The line is split up on every occurrence of one or more commas into substrings (tokens) with assigning first comma delimited string to specified loop variable A. The first solution additionally assigns eighth comma delimited string to next loop variable B according to ASCII table.
The inner FOR processes the string assigned to loop variable A with using / as string delimiter to get assigned to specified loop variable file: and to next loop variable according to ASCII table the rest of the string after first sequence of forward slashes which is the full qualified file name.
The full qualified file name is output with command echo and appended either directly to file output2.txt (first solution) or first to a memory buffer which is finally at once written into file output2.txt overwriting a perhaps already existing file with that file name in current directory.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
del /?
echo /?
for /?
See also the Microsoft article about Using command redirection operators for an explanation of the redirections >, >> and 2>nul

capture result of wmic command to variable [duplicate]

This question already has answers here:
Set the value of a variable with the result of a command in a Windows batch file [duplicate]
(6 answers)
Assign command output to variable in batch file [duplicate]
(5 answers)
Closed 3 years ago.
I am trying to write a small batch script to install an app from a thumb drive. The problem is the drive letter changes when plugged into different machines depending on the available drive letters. I have the script to run the installation but would like to add a script to the beginning that would detect the assigned drive letter of my inserted thumb drive and store it in a variable that I could then substitute in the rest of the script for the drive letter to complete the installation.
I got the command to identify the assigned drive letter of the thumb drive which works on its own.
wmic logicaldisk where volumename="StacelandFlash" get name
Result: D: (correct)
But I can't seem to assign it to a variable.
set X=wmic logicaldisk where volumename="StacelandFlash" get name
echo X
Result: X
set X=wmic logicaldisk where volumename="StacelandFlash" get name
echo %X%
Result: wmic logicaldisk where volumename="StacelandFlash" get name
Firstly, to capture the output of a command to a variable use for /F. See for /? in a cmd console for full details. Example:
for /f "tokens=2 delims=:" %%I in ('find /c /v "" "notes.txt"') do (
set /a "linecount=%%I"
)
rem // %linecount% now contains the number of lines in notes.txt
Now, there are a couple of complications unique to capturing WMI query results. Firstly, your wmic command includes an equals sign, which will break a for /f. That part's easy enough to fix: either escape the = with a caret (e.g. ^=), or just surround the equation in quotation marks.
The next hurdle is a bit trickier. WMI results are encoded in a non-ANSI encoding (UCS-2 LE). Capturing the output of wmic also captures the output's encoding, resulting in the last character being moved to the beginning of the line or other unexpected behavior. The workaround for that is to use a second nested for /f to sanitize the value.
With all that in mind, I think this is what you're looking for:
#echo off
setlocal
for /f "tokens=2 delims==" %%I in (
'wmic logicaldisk where "volumename='StacelandFlash'" get name /value'
) do for /f "delims=" %%# in ("%%I") do set "driveletter=%%~#"
echo %driveletter%
Note: credit #Dave Benham for discovering this workaround.
You need to run the command within a For Loop.
#Echo Off
For /F "Skip=1 Delims=" %%A In ('
"WMIC LogicalDisk Where (VolumeName='StacelandFlash') Get Name"
') Do For %%B In (%%A) Do Set "USB=%%B"
Echo(%USB%
Timeout -1
I suppose the batch file executed is stored on the thumb drive. And this batch file is executed with a double click. Therefore all you need is:
set "DriveLetter=%~d0"
%~d0 references the drive of argument 0 which is the batch file name. Run in a command prompt window call /? for details on how to reference arguments of a batch file. %~d0 expands to D:, E:, ...

Windows batch file - splitting a string to set variables

I feel like I'm going around in circles with FOR loop options.
I'm trying to take a string (output of a command) and split it on commas, then use each value to SET, e.g.
String: USER=Andy,IP=1.2.3.4,HOSTNAME=foobar,PORT=1234
So I want to split on comma and then literally use that variable in SET. I don't know ahead of time how many many variables there will be.
I've tried things like:
FOR %%L IN (%MYSTRING%) DO ECHO %%L
but that splits on the equals sign too so I end up with
USER
Andy
IP
1.2.3.4
etc
I just want to be able to do the following so I can SET USER=Andy etc, something like:
FOR %%L IN (%MYSTRING%) DO SET %%L
What option or flags am I missing?
The default delimiters for elements in plain FOR command (no /F option) are spaces, tab, commas, semicolons and equal signs, and there is no way to modify that, so you may use FOR /F command to solve this problem this way:
#echo off
set MYSTRING=USER=Andy,IP=1.2.3.4,HOSTNAME=foobar,PORT=1234
:nextVar
for /F "tokens=1* delims=," %%a in ("%MYSTRING%") do (
set %%a
set MYSTRING=%%b
)
if defined MYSTRING goto nextVar
echo USER=%USER%, IP=%IP%, HOSTNAME=%HOSTNAME%, PORT=%PORT%
Another way to solve this problem is first taking the variable name and then executing the assignment for each pair of values in a regular FOR command:
setlocal EnableDelayedExpansion
set MYSTRING=USER=Andy,IP=1.2.3.4,HOSTNAME=foobar,PORT=1234
set varName=
for %%a in (%MYSTRING%) do (
if not defined varName (
set varName=%%a
) else (
set !varName!=%%a
set varName=
)
)
echo USER=%USER%, IP=%IP%, HOSTNAME=%HOSTNAME%, PORT=%PORT%
EDIT 2023/01/20: New method added
I know this is a very old question. However, I can't resist the temptation to post a new very interesting method to solve this old problem:
#echo off
set MYSTRING=USER=Andy,IP=1.2.3.4,HOSTNAME=foobar,PORT=1234
set "%MYSTRING:,=" & set "%"
echo USER=%USER%, IP=%IP%, HOSTNAME=%HOSTNAME%, PORT=%PORT%
If you want to know where the magic is, remove the #echo off line, execute the program and carefully review the screen...
In case your input is something like HOSTNAME:PORT and you need to split into separate variables then you can use this
#echo off
set SERVER_HOST_PORT=10.0.2.15:8080
set SERVER_HOST_PORT=%SERVER_HOST_PORT::=,%
for /F "tokens=1* delims=," %%a in ("%SERVER_HOST_PORT%") do (
set SERVER_HOST=%%a
set SERVER_PORT=%%b
)
echo SERVER_HOST=%SERVER_HOST%
echo SERVER_PORT=%SERVER_PORT%

Windows batch scripting: compare two files' created dates

I would like to fork my Windows batch script based on a comparison of the created dates of two files, and I'm not sure where to begin. I feel like there must be a way. Any ideas?
UPDATE:
Tried the solution in PA's answer. I copied the code snippet verbatim to the end of my current script. Then, I added this early in the script:
IF EXIST "%PROGRAMFILES(X86)%" CALL :getCreationDate "%PROGRAMFILES(X86)%\oracle\jinitiator 1.3.1.28\lib\security\certdb.txt"
which gives me an error when I execute:
Invalid alias verb.
You need to put a caret before the equals sign to escape it (cmd.exe is sooo wonderful). I've verified this works:
setlocal enableextensions enabledelayedexpansion
call :getCreationDate "C:\Windows\Notepad.exe"
echo Creation Date is: %creationdate%
endlocal
goto :EOF
:getCreationDate
set FILE=%~f1
set FILE=%FILE:\=\\%
for /F "skip=1 tokens=* usebackq" %%A in (`wmic datafile where name^="%FILE%" get creationdate`) do (
set creationdate=%%A
)
goto :EOF
In a bat you can get the creation date of a file with WMIC DATAFILE command, using the GET CREATIONDATE verb.
You need to capture the output of the command into a variable, see HELP FOR and HELP SET.
You can use :label and GOTO :eof to create a function that puts together this functionality.
Notice that for WMIC DATAFILE, the WHERE NAME= clause requires a fully specified filename. See HELP CALL and the %~f modifier.
Notice also that WMIC DATAFILE WHERE NAME= requires doubling the backslashes in the filename. See HELP SET and the % : = % syntax for replacing single backslashes with double backslashes.
something like this.....
:getCreationDate
set FILE=%~f1
set FILE=%FILE:\=\\%
FOR /F "skip=1 tokens=* usebackq" %%A IN (`wmic datafile where name="%FILE%" get creationdate`) DO (
SET CREATIONDATE=%%A
)
goto :eof
You will need to use CALL :label for invoking it.
CALL :getCreationDate myfile.txt
You'll need to extract the part of the datetime you are interested in compating. See HELP SET using the ~ modifier.
Finally, you'll need to compare the returned datefiles. See HELP IF.
try this:
wmic datafile where name='c:\\users\\ovadia\\test\\addx.txt' get 'LAST MODIFIED' > dateofNEWadd.txt
wmic datafile where name='c:\\users\\ovadia\\test\\prevaddx.txt' get 'LAST MODIFIED' > dateofOLDadd.txt
fc /LB1 dateofNEWadd.txt dateofOLDadd.txt
if errorlevel 1 echo "fc err not 0"
del dateof*
attributes for the 'get' may be...
Access Rights,
Caption,
Class Name,
Compressed,
Compression Method,
Computer System Class Name,
Computer System Name,
Creation Date,
Current File Open Count,
Description,
Drive,
Eight Dot Three File Name,
Encrypted,
Encryption Method,
File Extension,
File Name,
File System Class Name,
File System Name,
File Type,
Hidden,
Install Date,
Last Accessed,
Last Modified,
Manufacturer,
Name,
Path,
Readable,
Should Be Archived,
Size,
Status,
System File,
Version,
Writeable

Resources