Batch file variable inside if block - batch-file

I am trying to detect the drive letter that contains removable media (i.e. a memory stick) via wmic logicaldisk get caption^,description^,drivetype 2^>NUL then I want to use dir to get the name of the file on the drive (there should only be one but I don't know what the name is) and pass that filename into netsh wlan add profile.
I have this batch file I have written:
#echo off
for /F "usebackq tokens=1,2,3,4 " %%i in (`wmic logicaldisk get caption^,description^,drivetype 2^>NUL`) do (
if %%l equ 2 (
SET file= | dir %%i /b
echo %%i\%file%
netsh wlan add profile filename="%%i\%file%" user=all
)
)
pause
and I expect the output to be D:\%some file%.xml but I am only getting D:. It seems that the variable %file% is not being set correctly.
I've tried many variations but I can't get it to set properly. Any suggestions welcome!

Although I do not exactly know what you are trying to accomplish, I decided to provide a modified script in which some issues are fixed. As soon as you edit your question and clarify it I will update my answer accordingly.
So here is the updated code:
#echo off
setlocal EnableDelayedExpansion
for /F "skip=1 delims=" %%L in ('
2^> nul wmic LogicalDisk ^
WHERE ^(DriveType^=2^) ^
GET Caption^,Description
') do (
for /F "tokens=1,*" %%I in ("%%L") do (
set file=myfile.xml
echo %%I\!file!
)
)
endlocal
pause
skipped header line from wmic by the skip option of for /F;
implemented a WHERE clause to the wmic command line to filter out DriveType and so to not need the if condition;
put token Description to the last position by removing DriveType, so for /F token string * can be used, as the property value may contain delimiters (spaces) on its own (although %%J is not used in the loop then);
added a second for /F loop to remove artefacts from conversion of Unicode output of wmic by first for /F;
set variable file to a constant value myfile.xml just to demonstrate delayed expansion (see also setlocal command); of course file could be set in advance outside of the loop here;
removed the pipe stuff SET file= | dir %%i /b as I have no clue what this is intended to do;
Update
After the question has been revised and clarified I can provide a solution for the requested task:
#echo off
for /F "skip=1 delims=" %%L in ('
2^> nul wmic LogicalDisk WHERE ^(DriveType^=2^) GET Caption
') do (
for /F "tokens=1" %%I in ("%%L") do (
for /F "eol=| delims=" %%F in ('dir /B "%%I\"') do (
set "FILE=%%I\%%F"
)
setlocal EnableDelayedExpansion
netsh wlan add profile filename="!FILE!" user=all
endlocal
)
)
pause
removed property Description from wmic command line as it is not used anyway;
inserted another for /F loop to capture the output of dir /B;
the built file path in FILE is passed over to netsh wlan add profile using delayed expansion, because this is required when writing and reading a variable in the same block of code; normal expansion would return the value present at the time the entire block is parsed by the command interpreter; I enabled delayed expansion inside of the loop structure in order to avoid loss of exclamation marks in the file name, because while reading for variables like %%F, delayed expansion need to be disabled to not lose such characters;
actually the interim variable FILE and so delayed expansion would not be necessary if you could guarantee that there is one file available on each drive; if this is the case, remove the setlocal and endlocal command lines, move the netsh command line into the inner-most for /F %%F loop, replace !FILE! by %%I\%%F and remove the set command line;

Related

Extracting specific row from file windows command line

I have to combine csv files into 1 file using a batch file.
The problem is that I only need the third row from each file.
How can I extract a specific row from a csv file in the windows command line / batch file?
I have found skip as command to skip lines from start of file, but I need to also skip the files from after the line I need.
This is my current code (after converting from xls to csv):
for %%i in (headers.csv) do (
for /f "delims=" %%j in ('type "%%i"') do echo %%j >> combined.csv
)
for %%i in (file*.csv) do (
for /f "skip=2 delims=" %%j in ('type "%%i"') do echo %%j >> combined.csv
)
for %%i in (headers.csv) do (
set "flag=Y"
for /f "delims=" %%j in ('type "%%i"') do if defined flag set "flag="&echo %%j >> combined.csv
)
for %%i in (file*.csv) do (
set "flag=Y"
for /f "skip=2 delims=" %%j in ('type "%%i"') do if defined flag set "flag="&echo %%j >> combined.csv
)
flag is set to something each time a new file is chosen. The first 2 lines of that file are then skipped, and on the third flag is cleared and the line reproduced. Once flag is cleared (becomed not-defined) the echo won't happen.
if defined works on the current (ie run-time) value of the variable, not the initial (parse-time) value.
#echo off
setlocal enableextensions disabledelayedexpansion
(
for /f "tokens=1,* delims=:" %%a in ('
findstr /n "^" headers.csv file*.csv ^| findstr /r /c:"^[^:]*:3:"
') do (
set "line=%%b"
setlocal enabledelayedexpansion
echo(!line:~2!
endlocal
)
) > combined.csv
It will use findstr to retrieve the full content from all the indicated files (I was not sure and I've also included the headers file), with lines numbered. As multiple files are requested, the output from this first findstr command will be in the format
filename.ext:lineNumber:linedata
This is filtered by a second findstr using a regular expression to only retrieve the third line from each file.
These lines will be processed by the for loop, using the colon as a delimiter to remove the file name. To avoid problems with lines that could start with a colon, the line number and the aditional colon are removed with a substring operation.
If in the final code the reference to the files forces findstr to include in the output the full path to the files, the tokens clause needs to be changed to tokens=2,* to face the colon in the drive name.

Numbering text files in a folder

I have some text files placed in the same folder as a batch file. I need the batch file to echo the filenames without its extention line by line with a numbering. It has to be in alphabetical order. Also, it needs to store the file name into a variable fruitx where x is the number of its numbering. For example...
If I have three files in the folder:
Banana.txt
Cherry.txt
Apple.txt
I need the batch file to echo:
1) Apple
2) Banana
3) Cherry
Then, I need the variable %fruit1% be "Apple", %fruit2% be "Banana" and %fruit3% be "Cherry".
set x=0
for /F "tokens=* delims=" %%I in ('dir /b *.txt') do (
set /a x=x+1&echo %x%^) %%~nI&set fruit%x%=%%~nI)
echo %x%
Here is my code. It doesn't work and I can't figure out why. It echos 0 for each of the numberings instead.
Sorry if this sounds confusing. Thanks in advance!
When the cmd parser reads a line or a block of lines (the code inside the parenthesis), all variable reads are replaced with the value inside the variable before starting to execute the code. If the execution of the code in the block changes the value of the variable, this value can not be seen from inside the same block, as the read operation on the variable does not exist, as it was replaced with the value in the variable.
This same behaviour is seen in lines where several commands are concatenated with &. The line is fully parsed and then executed. If the first commands change the value of a variable, the later commands can not use this changed value because the read operation replace.
To solve it, you need to enable delayed expansion, and, where needed, change the syntax from %var% to !var!, indicating to the parser that the read operation needs to be delayed until the execution of the command.
In your case, the problematic variable is x, that changes its value inside the loop and needs this value inside the same loop
#echo off
setlocal enableextensions enabledelayedexpansion
set "x=0"
for /f "delims=" %%a in ('dir /a-d /b /on *.txt 2^>nul') do (
set /a "x+=1"
echo !x!^) %%~na
set "fruit!x!=%%~na"
)
echo(---------------------------
set fruit
Also, an alternative approach without delayed expansion can be to filter the generated list of files with findstr to generate the number for each element
#echo off
setlocal enableextensions disabledelayedexpansion
for /f "tokens=1,2 delims=:" %%a in ('dir /a-d /b /on *.txt 2^>nul ^| findstr /n "^"') do (
echo %%a^) %%~nb
set "fruit%%a=%%~nb"
)
echo(---------------------------
set fruit
#echo off
setlocal enableDelayedExpansion
set counter=0
for /f %%a in ('dir /b /a:-d /o:n *.txt') do (
set /a counter=counter+1
echo !counter! ^) %%~na
set "fruit!counter!=%%~na"
)
echo listing fruits
set fruit

Batch File - Getting Volume Labels in a For Loop

I'm trying to set up a batch file that will iterate through the local drives on a PC and get the Volume Name of each drive into an Environment Variable that I can then use for further processing.
Here's what I've got so far, but it doesn't return the correct value for the volume label into the VLABEL1 variable inside the For loop.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
for /f "tokens=2 delims==" %%D in ('wmic logicaldisk where "drivetype=3" get name /format:value') do (
echo Processing Drive %%D
call :GetLabel %%D VLABEL1
echo The Label of Volume %%D is !VLABEL1!
echo.
)
endlocal
goto :EOF
:GetLabel
setlocal
for /f "tokens=5*" %%A in ('vol "%~1"^|find "Volume in drive "') do (
set VOLLABEL=%%B
)
set %2=%VOLLABEL%
endlocal
goto :EOF
Needles to say I've tried various combinations & permutations, but without success, so I'd appreciate any suggestions.
Try to run this from command line and you will see the reason
for /f "tokens=2 delims==" %a in ('wmic logicaldisk where "drivetype=3" get name /format:value') do echo .[%a].
There is an aditional carriage return at the end of each of the lines.
So, it is necessary to eliminate it before calling your subroutine.
for /f "tokens=2 delims==" %%A in (
'wmic logicaldisk where "drivetype=3" get name /format:value'
) do for %%D in (%%A) do (
....
)
...
The aditional for loop removes the CR so now you have the correct value in the variable to call your subroutine.
Now, inside your subroutine,
set %2=%VOLLABEL%
endlocal
The endlocal cancels the assignment of the upper line. It is its mission, cancel changes made in the environment since the previous setlocal. So, it is necessary to both, change the variable and cancel the non required changes. To do so, the fact that the parser works on lines can be used. So the code should be written as
endlocal & set "%2=%VOLLABEL%"
The full line is readed, at parse time all the value reads are replaced with values and then the line is executed. So, when the second command in the line (the set) is executed, there is no access to the %VOLLABEL% variable. This read has been replaced with the value of the variable before the endlocal were executed.

How to randomly rearrange lines in a text file using a batch file

I am creating a code that strips through different MAC addresses randomly, but cannot figure out how to do this. My thought on how to approach this is to randomize or rearrange the order of the MAC address in the text file with this script, but I cannot figure out how to do this with a batch file. How this will work is that it will read "maclist.txt", then create a new temp file with the random order "maclist_temp.txt", that will be the rearranged file. Then, it will pull this randomized file in order.
I have tried Google and searching the web, but I haven't found anything too useful. I'm still actively looking, but any advice would be extremely useful.
Something as simple as extracting and deleting a random line and then adding to the bottom might work. Randomization would be better though, but I want to keep the original list. Something like:
Make a temp copy of maclist.txt called maclist_temp.txt
Take one random MAC address, remove it from maclist_temp.txt
Readd it to the bottom
That is all I want, but any suggestions are welcome.
You may try this batch file to help you to shuffle your maclist.txt. The usage of the batch code is
C:\> type list.txt | shuffle.bat > maclist_temp.txt
Here are the contents of shuffle.bat:
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET TmpFile=tmp%RANDOM%%RANDOM%.tmp
TYPE NUL >%Tmpfile%
FOR /F "tokens=*" %%i IN ('MORE') DO SET Key=!RANDOM!!RANDOM!!RANDOM!000000000000& ECHO !Key:~0,15!%%i>> %TmpFile%
FOR /F "tokens=*" %%i IN ('TYPE %TmpFile% ^| SORT') DO SET Line=%%i&ECHO.!Line:~15!
::DEL %TmpFile%
ENDLOCAL
After issuing the above command, maclist_temp.txt will contain a randomized list of MAC addresses.
Hope this helps.
Here is a simpler method to randomize/randomise a file, no temp files needed. You can even reuse the same input filename.
Limitations are: blank lines and line starting with ; will be skipped, and lines starting with = will have all leading = signs stripped and ^ characters are doubled.
#echo off
setlocal
for /f "delims=" %%a in (maclist.txt) do call set "$$%%random%%=%%a"
(for /f "tokens=1,* delims==" %%a in ('set $$') do echo(%%b)>newmaclist.txt
endlocal
I really like foxidrive's approach. Nevertheless I want to provide a solution with all the listed limitations eliminated (although cmd-related restrictions like file sizes < 2 GiB and line lengths < ~ 8 KiB remain).
The key is delayed expansion which needs to be toggled to not lose explamation marks. This solves all the potential problems with special characters like ^, &, %, !, (, ), <, >, | and ".
The counter index has been implemented in order not to lose a single line of the original text file, which could happen without, because random may return duplicate values; with index appended, the resulting variable names $$!random!.!index! are unique.
The findstr /N /R "^" command precedes every line of the original file with a line number followed by a colon. So no line appears empty to the for /F loop which would ignore such. The line number also implicitly solves the issue with leading semicolons, the default eol character of for /F.
Finally, everything up to and including the first colon (remember the said prefix added by findstr) is removed from every line before being output, hence no more leading equal-to signs are dismissed.
So here is the code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set /A "index=0"
for /f "delims=" %%a in ('findstr /N /R "^" "%~dpn0.lst"') do (
setlocal EnableDelayedExpansion
for /F %%b in ("$$!random!.!index!") do (
endlocal
set "%%b=%%a"
)
set /A "index+=1"
)
> "%~dpn0.new" (
for /f "delims=" %%a in ('set $$') do (
set "item=%%a"
setlocal EnableDelayedExpansion
echo(!item:*:=!
endlocal
)
)
endlocal
exit /B
This seems to work. Feed it a command line parameter of the file to randomize.
#echo off
setlocal EnableDelayedExpansion
rem read the number of lines in the file
rem the find prepends the line number so we catch blank lines
for /f "delims=" %%n in ('find /c /v "" %1') do set "len=%%n"
set len=!len:*: =!
rem echo %1 has %len% lines
rem Relocate as many lines as there are lines in the file
for /l %%j in (1 1 !len!) do (
rem echo starting round %%j
rem geta random number between 1 and the number of lines in the file
set /a var=!random! %% !len! + 1
rem echo relocating line !var!
rem make sure there is no temp file
if exist %1.temp del %1.temp
rem read each line of the file, write any that don't match and then write the one that does
<%1 (
for /l %%i in (1 1 !len!) do (
rem if it is the target line then save it
if %%i == !var! (
set /p found=
rem echo saving !found!
)
rem if it is the target line then write it
if not %%i == !var! (
set /p other=
rem echo writing !other!
echo !other!>> %1.temp
)
)
rem now write the target line at the end
rem echo appending !found!
echo !found!>> %1.temp
)
rem replace the original with the temp version
move %1.temp %1>nul
)
rem print the result
type %1
Place in cmd file
for /f "tokens=2 delims=/" %%m in ('cmd /e:on /v:on /c "for /f %%f in (maclist.txt) do #echo !random!/%%f" ^| sort') do echo %%m
It spawns a cmd which reads the mac list in the inner for, prefixes a random value and a slash to the mac and sorts the list. Then this list is splitted in the outter for using the slash as delimiter and printing the mac address.

batch script - read line by line

I have a log file which I need to read in, line by line and pipe the line to a next loop.
Firstly I grep the logfile for the "main" word (like "error") in a separate file - to keep it small. Now I need to take the seperate file and read it in line by line - each line needs to go to another loop (in these loop I grep the logs and divide it in blocks) but I stuck here.
The log looks like
xx.xx.xx.xx - - "http://www.blub.com/something/id=?searchword-yes-no" 200 - "something_else"
with a for /f loop I just get the IP instead of the complete line.
How can I pipe/write/buffer the whole line? (doesn't matter what is written per line)
Try this:
#echo off
for /f "tokens=*" %%a in (input.txt) do (
echo line=%%a
)
pause
because of the tokens=* everything is captured into %a
edit:
to reply to your comment, you would have to do that this way:
#echo off
for /f "tokens=*" %%a in (input.txt) do call :processline %%a
pause
goto :eof
:processline
echo line=%*
goto :eof
:eof
Because of the spaces, you can't use %1, because that would only contain the part until the first space. And because the line contains quotes, you can also not use :processline "%%a" in combination with %~1. So you need to use %* which gets %1 %2 %3 ..., so the whole line.
The "call" solution has some problems.
It fails with many different contents, as the parameters of a CALL are parsed twice by the parser.
These lines will produce more or less strange problems
one
two%222
three & 333
four=444
five"555"555"
six"&666
seven!777^!
the next line is empty
the end
Therefore you shouldn't use the value of %%a with a call, better move it to a variable and then call a function with only the name of the variable.
#echo off
SETLOCAL DisableDelayedExpansion
FOR /F "usebackq delims=" %%a in (`"findstr /n ^^ t.txt"`) do (
set "myVar=%%a"
call :processLine myVar
)
goto :eof
:processLine
SETLOCAL EnableDelayedExpansion
set "line=!%1!"
set "line=!line:*:=!"
echo(!line!
ENDLOCAL
goto :eof
This has worked for me in the past and it will even expand environment variables in the file if it can.
for /F "delims=" %%a in (LogName.txt) do (
echo %%a>>MyDestination.txt
)
For those with spaces in the path, you are going to want something like this:
n.b. It expands out to an absolute path, rather than relative, so if your running directory path has spaces in, these count too.
set SOURCE=path\with spaces\to\my.log
FOR /F "usebackq delims=" %%A IN ("%SOURCE%") DO (
ECHO %%A
)
To explain:
(path\with spaces\to\my.log)
Will not parse, because spaces.
If it becomes:
("path\with spaces\to\my.log")
It will be handled as a string rather than a file path.
"usebackq delims="
See docs will allow the path to be used as a path (thanks to Stephan).

Resources