I'm trying to do a Windows batch file that excecute a command and pass to it a flow param that is received as a parameter of the batch file, then I need to get the output of this execution and do something with it before sending it to the batch output (cuting the output to get the first character only).
This is what I have so far:
for /f %%i in ('"C:\Program Files (x86)\JAM Software\SpamAssassin for Windows\spamc.exe" < %1') do set RES2 = %%i
ECHO %RES2~0,1%
But it is not working as I expect. Thanks for the help.
for /f %%i in (
'"C:\Program Files (x86)\JAM Software\SpamAssassin for Windows\spamc.exe" ^< %1'
) do set RES2=%%i
ECHO %RES2:~0,1%
There are three changes in your code. The colon in the echo line (needed, it is part of the sintax), the escaped redirection and the spaces removed around the equal sign in the set line. With spaces, the variable name contains a space and the value inside the variable also have a space in it (and it will be the first, so the later echo command will fail)
Related
This question already has answers here:
Arrays, linked lists and other data structures in cmd.exe (batch) script
(11 answers)
Closed 3 years ago.
I have a list of paths from which I want to extract folder name
I wrote:
#echo off
set paths[0]="C:\p\test1"
set paths[1]="C:\p\test2"
set paths[2]="C:\p\test3"
(for %%p in (%paths%) do (
for %%F in (%%p) do echo Processing %%~nxF
))
but seems that nothing is shown.
I expected to see:
Processing test1
Processing test2
Processing test3
It makes a big difference if first " is specified on a set command line left to variable name or left to variable value. In most cases it is better to specify it left to the variable name, especially if a variable value holding a path should be concatenated later with a file name to a full qualified file name.
See also: Why is no string output with 'echo %var%' after using 'set var = text' on command line?
The solution for this task is:
#echo off
set "paths[0]=C:\p\test1"
set "paths[1]=C:\p\test2"
set "paths[2]=C:\p\test3"
for /F "tokens=1* delims==" %%I in ('set paths[ 2^>nul') do echo Processing %%~nxJ
The command FOR with option /F and a set enclosed in ' results in starting one more command process running in background with %ComSpec% /c and the command line specified between the two ' appended as further arguments. So executed is in this case with Windows installed to C:\Windows:
C:\Windows\System32\cmd.exe /c set paths[ 2>nul
The command SET outputs all environment variables of which name starts with paths[ line by line using the format VariableName=VariableValue to handle STDOUT of started background command process.
It could be that there is no environment variable of which name starts with paths[ which would result in an error message output to handle STDERR by command SET which would be redirected from background command process to handle STDERR of the command process which is processing the batch file and for that reason would be displayed in console window. For that reason a possible error message is redirected by the background command process to device NUL to suppress it with using 2>nul.
Read the Microsoft article about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded set command line with using a separate command process started in background.
FOR captures in this case everything written to handle STDOUT of started background command process and process this output line by line after started cmd.exe terminated itself.
Empty lines are ignored by FOR which does not matter here as there are no empty lines to process.
FOR would split up a non-empty line into substrings using normal space and horizontal tab as string delimiters and would assign just first space/tab separated string to specified loop variable, if it does not start with default end of line character ;. This default line splitting behavior is not wanted here. For that reason the option delims== defines the equal sign as string delimiter.
The option tokens=1* instructs FOR to assign in this case the variable name to specified loop variable I and assign everything after the equal sign(s) after variable name without any further string splitting on equal signs to next loop variable according to ASCII table which is in this case J. That is the reason why loop variables are interpreted case-sensitive while environment variables are handled case-insensitive by the Windows command processor.
In this case only the variable value is of interest in the body of the FOR loop. For that reason just loop variable J is used on ECHO command line while I is not used at all.
The modifier %~nxJ results in removing surrounding double quotes from string value assigned to loop variable J and next get the string after last backslash or beginning of string in case of the string value does not contain a backslash at all. This is the name of the last folder in folder path string.
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.
echo /?
for /?
set /?
UPDATE:
There is a big advantage of this solution in comparison to the other two solutions posted up to now here:
There is not used delayed environment variable expansion which is always problematic on working with file or folder names on not being 100% sure that no folder and no file contains ever an exclamation mark in its name.
Let us compare the three solutions with unusual folder names containing !.
#echo off
rem Make sure there is no environment variable defined of which name starts with
rem paths[ as suggested by Compo which is a very valuable addition on my code.
for /F "delims==" %%I in ('set paths[ 2^>nul') do set "%%I="
set "paths[0]=C:\p\test1!"
set "paths[1]=C:\p\!test2"
set "paths[2]=C:\p\!test!3"
echo/
echo Results of solution 1:
echo/
for /F "tokens=1* delims==" %%I in ('set paths[ 2^>nul') do echo Processing %%~nxJ
echo/
echo Results of solution 2:
echo/
SetLocal EnableDelayedExpansion
for /L %%i in (0,1,2) do (
for %%j in (!paths[%%i]!) do echo Processing %%~nxj
)
endLocal
echo/
echo Results of solution 3:
echo/
Setlocal EnableDelayedExpansion
Call :process paths "!paths[0]!" "!paths[1]!" "!paths[2]!"
Endlocal
echo/
pause
goto :EOF
:process
Set P_C=0
Set /a P_C-=1
For %%a in (%*) DO (
CALL :populate %1 "%%~a"
)
Set /a P_C-=1
For /L %%b in (0,1,!P_C!) DO (
ECHO Processing %1[%%b] = "!%1[%%b]!"
)
GOTO :EOF
:populate
Set "%1[!P_C!]=%~2"
Set /a P_C+=1
GOTO :EOF
The output on running this batch file is:
Results of solution 1:
Processing test1!
Processing !test2
Processing !test!3
Results of solution 2:
Processing test1
Processing test2
Processing 3
Results of solution 3:
Processing paths[0] = "C:\p\test1\p\\p\3"
Solution 1 as posted here works for all three folder names correct.
Solution 2 omits for first and second folder name the exclamation mark which will most likely cause errors on further processing. The third folder name is modified to something completely different. Enabled delayed expansion results in parsing a second time echo Processing %%~nxj after %~nxj being replaced by !test!3 with interpreting test in folder name now as environment variable name of which value is referenced delayed. There was no environment variable test defined on running this batch file and so !test!3 became just 3 before echo was executed by Windows command processor.
Solution 3 produces garbage on any folder name contains an exclamation mark, even on full qualified folder name defined before enabling delayed expansion and referenced with delayed expansion on calling the subroutine process.
Well, folder and file names with an exclamation mark in name are fortunately rare which makes the usage of delayed expansion usually no problem. But I want to mention here nevertheless the potential problems which could occur on any folder name containing one or more !.
Something like that should work :
#echo off
set paths[0]="C:\p\test1"
set paths[1]="C:\p\test2"
set paths[2]="C:\p\test3"
SetLocal EnableDelayedExpansion
for /L %%i in (0,1,2) do (
for %%j in (!paths[%%i]!) do echo Processing %%~nxj
)
pause
Define the Array within the function.
This approach can be used to define multiplay Arrays.
#ECHO OFF
Setlocal EnableDelayedExpansion
:: REM P_C is used to define the range of the Array. The -1 operations on P_C is to shift the paths parameter out of the Arrays working Index.
::REM the first parameter passed is used as the Arrays Name. all other parameters are assigned to index values 0 +
Call :process paths "C:\p\test1" "C:\p\test2" "C:\p\test3"
pause
:process
Set P_C=0
Set /a P_C-=1
For %%a in (%*) DO (
CALL :populate %1 "%%~a"
)
Set /a P_C-=1
For /L %%b in (0,1,!P_C!) DO (
ECHO Processing %1[%%b] = "!%1[%%b]!"
)
GOTO :EOF
:populate
Set "%1[!P_C!]=%~2"
Set /a P_C+=1
GOTO :EOF
I'm trying to run a query with a FOR loop and have each resulting variable line of the query output into it's own file.
I can't seem to get the delayed expansion variables to behave the way I want them to.
Basically I'm querying the printers installed on a print server and I want a .bat job containing certain text with the result output to multiple files containing the result name.
I think the problem is I'm not escaping some characters correctly, or I'm missing a % or ! on a variable, or some combination thereof.
#echo off
setlocal enabledelayedexpansion
FOR /F "tokens=1 delims=*" %%G IN ('wmic /node:PRINTSERVER printer get name') DO (
SET printer=%%G
SET printers=!printers: =!
SET printers=!printers:Name=!
ECHO ^(
#ECHO OFF
ECHO PLEASE WAIT WHILE YOUR PRINTER IS INSTALLED
ECHO.
ECHO ^"%windir%\system32\cscript %windir%\system32\prnmngr.vbs -ac -p \\PRINTSERVER\!printer!"^)>>!printer!.bat
)
endlocal
Expected results should be multiple files named PRINTERSHARENAME.bat
Each which contains:
#ECHO OFF
ECHO PLEASE WAIT WHILE YOUR PRINTER IS INSTALLED
ECHO.
%windir%\system32\cscript %windir%\system32\prnmngr.vbs -ac -p "\\PRINTSERVER\PRINTERSHARENAME"
EDIT
I will share more of my code. the wmic output contains spaces that had to be stripped, so this is why I used enabledelayedexpansion
EDIT2
Here is the output of my wmic command (Note that there are trailing spaces I've stripped out in the above code and the word 'Name' and a blank line at the end of the command):
C:\Users\bleepbloop>wmic /node:PRNTSVR printer get name
Name
PRINTER1
PRINTER2
OFFICEPRINTER
EDIT3
OK, I'm getting close. Here is code to reproduce, using an answer below:
(
echo Here is my first line
echo Here is my second line
echo Here is my third line
)>"textfile.txt"
FOR /F "delims=" %%G IN ('TYPE textfile.txt') DO (
(
ECHO #ECHO OFF
ECHO ECHO PLEASE WAIT WHILE YOUR PRINTER IS INSTALLED
ECHO ECHO.
ECHO "%%windir%%\system32\cscript" "%%windir%%\system32\prnmngr.vbs" -ac -p \\PRINTSERVER\%%G
)>%%G.bat
)
This works as expected and gives me 3 files named
Here is my first line.bat
Here is my second line.bat
Here is my third line.bat
however now I want to strip out all spaces from the variables output by textfile.txt, and for that I think I need to use delayed expansion?
So I want:
Hereismyfirstline.bat
Hereismysecondline.bat
Hereismythirdline.bat
I think I need to do this by using enabledelayedexpansion and inserting the following in the FOR loop:
SET variable=%%G
SET variable=!variable: =!
and then I have to insert the variable back into the loop properly. Still not sure how.
I want the file
Hereismyfirstline.bat
to contain
#ECHO OFF
ECHO PLEASE WAIT WHILE YOUR PRINTER IS INSTALLED
ECHO.
"%windir%\system32\cscript" "%windir%\system32\prnmngr.vbs" -ac -p \\PRINTSERVER\Hereismyfirstline
and the next file
Hereismysecondline.bat
to contain:
#ECHO OFF
ECHO PLEASE WAIT WHILE YOUR PRINTER IS INSTALLED
ECHO.
"%windir%\system32\cscript" "%windir%\system32\prnmngr.vbs" -ac -p \\PRINTSERVER\Hereismysecondline
I suggest this batch code for this task:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "skip=1 eol=| tokens=*" %%I in ('%SystemRoot%\System32\wbem\wmic.exe /node:PRINTSERVER printer get name 2^>nul') do (
for /F "eol=| delims= " %%J in ("%%I") do (
set Printer=%%~nxJ
if defined Printer (
echo #echo off
echo echo PLEASE WAIT WHILE YOUR PRINTER IS INSTALLED
echo echo/
echo "%%SystemRoot%%\system32\cscript.exe" "%%SystemRoot%%\system32\prnmngr.vbs" -ac -p "\\PRINTSERVER\%%~nxJ"
)>"%%~nxJ.bat"
)
)
endlocal
Note: After delims= on second for command line must be a horizontal tab character and not one or more spaces as displayed by the web browsers according to HTML standard for the tab character.
Processing output of wmic is problematic because of this application outputs data always Unicode encoded using UTF-16 Little Endian encoding and for has a quirks on processing this Unicode output correct. For more details see for example How to correct variable overwriting misbehavior when parsing output?
The outer for processes the output of wmic with skipping first line containing heading Name. For all other non-empty lines all leading normal spaces and horizontal tabs are removed before assigning the rest of the line to specified loop variable I even on starting very unusual with a semicolon. | is not allowed in a printer name, but ; would be allowed although having never seen a printer name starting with ;.
The inner for with horizontal tab character as delimiter processes the printer name which can contain one or more spaces, but not a tab character. The printer name with all trailing horizontal tabs removed is assigned to specified loop variable J.
The remaining string is the printer name with trailing normal spaces. Windows prevents usually the creation of a file with trailing spaces. For that reason is assigned to environment variable Printer with using %%~nxJ just the printer name without trailing spaces. But spaces inside the printer name are kept by this command.
A single carriage return caused by the for quirks on processing of output of wmic results in getting environment variable Printer deleted instead of being defined with the carriage return.
So if the environment variable Printer is really defined with a printer name containing no, one or even more spaces, but no leading spaces/tabs and no trailing spaces/tabs, the batch file can be created with using printer name as assigned to loop variable J in batch file and in batch file name using %%~nxJ.
So there is no delayed environment variable expansion needed which makes this batch file working also for a printer name containing an exclamation mark.
See also DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/ for the reason writing into the created batch files echo/ instead of echo..
This batch code uses delayed expansion inside the inner loop to remove all spaces from printer name for the batch file name.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "skip=1 eol=| tokens=*" %%I in ('%SystemRoot%\System32\wbem\wmic.exe /node:PRINTSERVER printer get name 2^>nul') do (
for /F "eol=| delims= " %%J in ("%%I") do (
set "Printer=%%~nxJ"
if defined Printer (
setlocal EnableDelayedExpansion
set "BatchFileName=!Printer: =!"
(
echo #echo off
echo echo PLEASE WAIT WHILE YOUR PRINTER IS INSTALLED
echo echo/
echo "%%SystemRoot%%\system32\cscript.exe" "%%SystemRoot%%\system32\prnmngr.vbs" -ac -p "\\PRINTSERVER\!Printer!"
)>"!BatchFileName!.bat"
endlocal
)
)
)
endlocal
It is absolutely no problem to run a batch file with one or more spaces in file name on enclosing entire batch file name in double quotes like any other file/folder name containing a space or one of these characters &()[]{}^=;!'+,`~. But this second batch file code demonstrates that it is possible to create the batch files with no spaces in file name while exclamation marks in printer name are nevertheless processed correct.
This code is slower than first code because of usage of setlocal and endlocal inside the loop. Read this answer for details about the commands setlocal and endlocal and what happens on every usage of these two commands in background making the second variant slower than the first variant.
Note: The printer name inside the batch file is with spaces. Just the batch file name is without spaces. But that can be easily changed if needed.
Parenthesis can group commands, but not split arguments. So, instead of ECHO (..., do:
SET printer=%%G
(
ECHO #ECHO OFF
ECHO ECHO PLEASE WAIT WHILE YOUR PRINTER IS INSTALLED
ECHO ECHO.
ECHO ^"%windir%\system32\cscript %windir%\system32\prnmngr.vbs -ac -p \\PRINTSERVER\!printer!"
)>!printer!.bat
Replaced >> with > which will create new files for each printer, instead of
appending more commands to existing files.
Noticed also you don't need delayed expansion, and probably "delims=" is more adequate.
Overall, your code may be rewritten as:
FOR /F "delims=" %%G IN ('wmic /node:PRINTSERVER printer get name') DO (
(
ECHO #ECHO OFF
ECHO ECHO PLEASE WAIT WHILE YOUR PRINTER IS INSTALLED
ECHO ECHO.
ECHO "%%windir%%\system32\cscript" "%%windir%%\system32\prnmngr.vbs" -ac -p \\PRINTSERVER\%%G
)>%%G.bat
)
I wanted to take list of files to delete from user as a argument. One line per argument.
How can store the list of files separated by new line in a variable.
I am using below command.
Set DeletionFiles=${p:DeleteFiles}"
for %%i in (%DeletionFiles%) do (
echo %%i
)
Then i wanted to iterated them on a loop.
${p:DeleteFiles} will get replaced by it's value from external app, which will contain list of files separated by new line.I can not change it.
#ECHO OFF
SETLOCAL
SET "deletionfiles="
:dloop
SET "deleteme="
SET /p "deleteme=Which file to delete ? "
IF DEFINED deleteme SET "deleteme=%deleteme:"=%"
IF DEFINED deleteme SET "deletionfiles=%deletionfiles%,"%deleteme%""&goto dloop
ECHO delete %deletionfiles:~1%
GOTO :EOF
There is no need to use a newline. Your for command (or a del command) will operate perfectly happily on a comma-(or space-)separated list.
Note that there are certain characters that batch uses for special purposes and batch string-processing may not process them in the expected manner. These characters include % ^ and &.
${p:DeleteFiles} will get replaced by it's value from external app,
which will contain list of files separated by new line.I can not
change it.
After the replacement the batch file looks like:
Set DeletionFiles=file1.jpg
file2.jpg
file3.jpg
"
This isn't a valid batch file anymore.
Furthermore it's a bad idea to modify the batch file itself, as this works only once.
You could place the ${p:DeleteFiles} into another file, like input.txt.
Your batch would look like
echo ${p:DeleteFiles} > input.txt
<external program for replacing the DeleteFiles> input.txt
for /F "tokens=*" %%A in (input.txt) do (
echo File: %%A
)
If I understand you correctly, your external program will generate a list of files. You then want to store this multi-line list to a variable. What do you want to do with the variable once you have it? I assume you want to delete the files, but your question isn't clear on that point, so I'll try to over-answer to cover it.
for /f "delims=" %%a in ('{command that generates your list}') do (
echo Doing stuff to %%a...
echo %%a>>listOfFilesToDelete.txt
set var=%%a
if "%var:~0,7%"="DoNotDelete" copy "%%a" \someArchiveFolder\
del "%%a"
)
This will read each line in your generated list as variable %%a. It will then do whatever command(s) you specify. This way, you can run a command on each of the files in the list. In the above code it's
Printing each line to the console embedded in some text
Outputting it to a file
Checking the first 7 characters of the line against a specified string and then copying it to a folder if it matches
And then deleting it
If you still need to reference each line from your generated list, you can even setup an array-like structure. (See Create list or arrays in Windows Batch)
setlocal EnableDelayedExpansion
:: Capture lines in an 'array'
set /a i=0
for /f "delims=" %%a in ('dir /b') do (
set /a i+=1
set var!i!=%%a
)
:: Loop through the 'array'
for /L %%a in (1,1,%i%) do (
echo Do more stuff with !var%%a!
)
Just like above, this will read each line in your generated list as variable %%a. It will then set a variable var!i! equal to the value of the current line. You can then reference each line as var1, var2, and so on, or, as the second section shows, you can loop through them all using for /L. You'll need to get a grasp on working with delayed expansion variables, though.
I am reading a text file %tmp%\tmp.txt. I am trying to find a line with a given string (in this example "RemotePath") and it sets the variable netdrive to that line if it is found. Here is my code:
for /f "tokens=*" %%a in (%tmp%\tmp.txt) do echo.%%a|findstr /C:"RemotePath " >nul 2>&1 && set netdrive=%%a
It never sets netdrive to anything, however when i change the set netdrive=%%a to echo %%a >> netdrive.txt the text I want does come up in the text file. Any ideas?
for /f "tokens=*" %%a in ('findstr /C:"RemotePath " "%tmp%\tmp.txt"') do set "netdrive=%%a"
The "problem" with the original code is that each side of a pipe is executed in a separate process, so, in your case, the set command is running in another cmd instance and the variable is set in this separate instance, not in the instance that runs the batch file.
It looks like you are repeatedly setting netdrive to each token from the input file, not the output of findstr. The final assignment probably gives it an empty value from the end of the file. To assign it from the output of findstr, see here: How do I get the result of a command in a variable in windows?
I would like to create a logfile with this information :
echo %time%;%date%;%computername%;"findstr /I "%%a" %MYFILES%\Dir_ALL.txt" >> %computername%_File.csv
How can I write something that will not write "findstr /I ..." but the output of this command ?
I would like to have everything in the same line on my output file.
Thank you
write a line without linefeed with set /p, followed by the second line:
<nul set /p .=%time%;%date%;%computername%;>>%computername%_File.csv
findstr /I "%%a" %MYFILES%\Dir_ALL.txt>>%computername%_File.csv
Note: Because of the %%a I guess, you are using this codefragment inside a forstatement. I suggest using !time! instead of %time% to get the actual time (using delayed expansion of course)
You have to set a variable with the command result. This is answered in Windows Batch help in setting a variable from command output, using a dummy for-loop whose loop variable is set from the content of a file.
That would be something like this:
findstr /I "%%a" %MYFILES%\Dir_ALL.txt >%temp%\temp.txt
for /f %%i in (%temp%\temp.txt) do echo %time%;%date%;%computername%;%%i >> %computername%_File.csv
del %temp%\temp.txt
There is a limitation on this: the variable cannot contain multiple lines. However, rephrasing your script fragment as a loop would probably solve that issue as well.
The MSDN article on set shows some additional features which you can use to control how the data from the file is parsed. Normally it parses the result into tokens separated by spaces. But you can override that using the delims keyword.