batch for loop not iterating beyond first entry - windows

My problem is pretty simple, but I'm unsure why I am seeing this behaviour. I want to get a list of parameters down to a single entry at a time so I can do some processing on them. The list of jar files I am processing is separated by a ; delimiter.
set JARS=this.jar;that.jar;and.jar;the.jar;other.jar
for /f "delims=;" %%a in ("%JARS%") do echo.%%a
I'm expecting the script to exit listing as follows.
this.jar
that.jar
and.jar
the.jar
other.jar
C:\>
But the script is instead exiting as follows.
this.jar
C:\>
I'm clearly missing something obvious, but I can't seem to see it.
I'm using Windows 7.

Try this: The semicolon is a separator on a command line so it will delimit the filenames.
set JARS=this.jar;that.jar;and.jar;the.jar;other.jar
for %%a in (%JARS%) do echo.%%a

Related

Capturing first 4 characters of a Windows command line output to variable

I'm trying to capture the first 4 characters of output from the following Windows command.
nltest /server:%COMPUTERNAME% /dsgetsite
What is normally returned would be:
SITEAdSiteName
This command completed successfully.
I've tried using the for /F command but can't seem to figure out how to strip everything else except the first 4 characters of what is returned.
I'm thinking using the for /F may not be the best way to accomplish this.
Are there other suggestions on how I many accomplish this?
I think my challenge is defining (or not) the delimiter to being any character, I've tried the *, but didn't seem to do it for me.
When I use this:
for /F "tokens=1-4 delims=*" %A in ('nltest /server:%COMPUTERNAME% /dsgetsite') DO echo %A
I get both output lines, sort of stumped here.
To store the first line of the output of nltest /server:%COMPUTERNAME% /DSGETSITE in variable LINE, use the following command line (use %%F instead of %F to use this in a batch file):
set "LINE=" & for /F %F in ('nltest /server:%COMPUTERNAME% /DSGETSITE') do if not defined LINE set "LINE=%F"
To return the first four characters, use sub-string expansion:
set "LINE=%LINE:~,4%"
echo %LINE%

Batch script for search & replace without skipping empty line, '!' and ';'?

Sorry, for bothering you for the (n+1)th time about search & replace with batch scripts.
I have text files (actually PS-files) (approx. 10kB-3MB) where I need to replace just a few numbers.
This should be easy, I thought.
I found quite a few scripts here on Stackoverflow but none of them worked properly so far. If I have overlooked THE "working one" please let me know.
The last one I tried:
#echo off
setlocal DisableDelayedExpansion
set OutputFile=%1
set OutputFile=%OutputFile:"=%
set InputFile=%OutputFile%.tmp
set SearchString=636170656C6C6133
set ReplaceString=636170656C6C6134
rem write empty file
type NUL > %OutputFile%
for /f "tokens=1,* delims=ΒΆ" %%A in ( '"type %InputFile%"') do (
SET string=%%A
setlocal EnableDelayedExpansion
SET modified=!string:%SearchString%=%ReplaceString%!
echo !modified!>>%OutputFile%
endlocal
)
del %InputFile%
First of all, it seems to be pretty(!) slow. I can see on disk how the file size increases.
The occurrences of the numbers seem to be replaced. However, the file is altered, which I easily can see from the different file size. As far as I can see, empty lines, exclamation marks and lines beginning with semicolon are skipped. This is messing up my file completely.
How to avoid this?
If I do the same thing with Perl I really get only the numbers altered, nothing else. However, I don't want to and cannot use Perl. I also don't want to use other extra programs or Windows-Powershell, since it should work on older systems too.
Is there any way to achieve this with a simple Windows batch script?
Thanks!
I believe the following is a working bat script that should not make any changes other than the desired number change:
#echo off
setlocal DisableDelayedExpansion
set "out=%~1"
set "in=%out%.tmp"
set "find=636170656C6C6133"
set "repl=636170656C6C6134"
>"%out%" (
for /f "delims=" %%A in ('findstr /n "^" "%in%"') do (
set "str=%%A"
setlocal EnableDelayedExpansion
set "str=!str:*:=!"
if defined str set "str=!str:%find%=%repl%!"
echo(!str!
endlocal
)
)
del "%in%"
Changes I have made:
Use %~1 to remove enclosing parentheses. Though technically, that is not necessary. Something like echo test >"someName.txt".new will work just fine.
FOR /F strips empty lines. I used FINDSTR to prefix each line with the line number, followed by a colon. Now there are no empty lines.
I use an extra variable expansion find/replace with * to remove the line number prefix.
Variable expansion find/replace will fail if a string is empty (undefined variable). So I verify the variable is defined before doing find/replace.
ECHOing an empty line, or line containing only white space, will result in ECHO is off. output. This is solved by using echo(
It takes time to initialize redirection, and your loop does this every iteration, which slows things down. I improved performance by enclosing the entire FOR loop in parentheses and redirecting only once.
You still may see a slight file size change for any of the following reasons
If the input has \n line terminators instead of \r\n.
If the last line of input is not terminated by \r\n. The script terminates all lines with \r\n, regardless what the input had.
The script will fail if any line contains a null byte, or if any line is >~8k length.
I hate editing text files with batch - it is complicated code, slow, and even the best possible solution still has significant limitations.
I recommend you use JREPL.BAT - a command line regular expression text processing utility. JREPL is pure script (hybrid batch/JScript) that runs natively on any Windows machine from XP onward - no 3rd party exe file or special configuration is needed.
The tool is very powerful, with many options. Full documentation is available from the command line via jrepl /?, or jrepl /?? for paged help.
Solving your problem with JREPL is trivial - you don't even need another script. The following command will work right from a command prompt:
jrepl 636170656C6C6133 636170656C6C6134 /f input.txt /o output.txt
Use CALL JREPL if you put the command within another batch script.
JREPL is way more powerful than what you need for this simple problem. But it is incredibly convenient, and once you have the utility, I suspect you will find many uses for it. Especially if you learn to use regular expressions, as well as the many JREPL options.
A VBS script
Set Inp = WScript.Stdin
Set Outp = Wscript.Stdout
Text = Inp.readall
Text = Replace(Text, "636170656C6C6133", "636170656C6C6134")
outp.write Text
To use
cscript //nologo script.vbs < input.txt > Output.txt
You use the right tool for the job. Batch is for starting programs and copying files.
The above is suited to the file sizes given. However if we are getting up to 100s of MB then this code is better.
Set Inp = WScript.Stdin
Set Outp = Wscript.Stdout
Do Until Inp.AtEndOfStream
Text = Inp.readline
Text = Replace(Text, "636170656C6C6133", "636170656C6C6134")
outp.writeline Text
Loop

`)`was unexpected at this time.

I am running a batch file on Windows 7 and running into this error (I have narrowed down the error to the following line):
FOR /F "delims=" %%I in ('echo %RegVal%') do set sasroot=%%~sI
Where Regval is the file path of a given software, which in this case (on my Win7 machine) is:
RegVal = C:\Program Files\SAS 9.2_M3_10w37\SASFoundation\9.2(32-bit)
This same script used to work on Windows Vista, although I suspect it may be that there a parenthesis in RegVal now as it was previousy C :\Program Files\SAS 9.2_M3_10w37\SASFoundation\ on my previous Vista machine.
You suspection is correct.
To get around it, enclose your variable into doublequotes (You remove them again with the ~ in the setcommand)
FOR /F "delims=" %%I in ('echo "%RegVal%"') do set sasroot=%%~sI
I suggest you create a file with the value of RegVal in it, then parse it using the FOR loop:
echo %RegVal%>C:\SomeFile.txt
FOR /F "delims=" %%I in (C:\SomeFile.txt) do set sasroot=%%~sI
This should help you get around your problem.
Stephan's solution is much simpler, but I'll explain my solution anyway, which might prove useful in some cases.
When the FOR command parses the data specified in the IN part using a command, it replaces the command with the result of the command, then runs the FOR command. For example, with the question above, the FOR command that will be executed after expanding echo %RegVal% is:
FOR /F "delims=" %%I in (C:\Program Files\SAS 9.2_M3_10w37\SASFoundation\9.2(32-bit)) do set sasroot=%%~sI
Thus, when the parser hits the first closing parenthesis, it will stop, thinking that everything it read before is the text to work on. However, in this case this is wrong, as the first closing parenthesis is part of the string to read; it doesn't indicate the end of the string.
When parsing a file with the FOR command, it will read each line, assign the predefined tokens with the correct values, then execute the code block that follows. Rinse and repeat for every line in the file. But in this case, it will not replace the IN part with each line; it will only parse it and assign values to the tokens. This is the reason why special characters (such as parenthesis) do not create parsing errors in this case.

cmd script printing out but not executing

Hi all I'm currently writing up a small bash script to automate some stuff for me but I've hit a bit of a snag My current file looks like the following:
for /f "delims=" %%f in ('dir /b "D:/*"') do C:\MediaInfo\MediaInfo.exe "--Inform=Video;%Width% "D:\%%f"
pause > nul
The pause thing is just there so I can see the output. While the part after the |do| command works fine if I manually type it in (as in I know my syntax for that is correct) however when running the batch script instead of actually executing the above commands it simply prints them out to the command console. Am I missing some syntax here or similar. Also as a side note I would like to push the resulting value of that query into an int so I can use it, do you know if this is possible in bash or should I look at trying to use a higher level language? Thanks!
I have no notion of the intricacies of mediainfo - but it would be unusual if it was to accept unbalanced quotes in its command line as you have posted. I'd suggest an extra after %Width%

Is it possible to use TWO spaces as a single Delimiter in CMD?

I don't think this is possible, but I'd like to be able to do this, or possibly use an alternative method...
I have a batch file;
for /f "usebackq tokens=*" %%a in (`wmic process get description, commandline`) do (
*Some Code*
)
I need to be able to take the two answers from each line, and use them individually (basically, use the description to check if a process is running, then after I've killed the process and done some file clean-up work, reload the original process including any command line parameters.
One example of the output for a process I may need to end/re-open might be;
"C:\some folder\some other folder\some_application" -cmd_parameter process_name.exe
Note that the descrption is clearly defined by multiple spaces..
So is there a way of saying
for /f "tokens=* delims= " <--(The delims is TWO spaces, not space OR space)
Another way that may be better could be to replcae all instances of multiple spaces with a special character (i.e. one that is never used in a proces or path), and then use that as my delimeter... Though I don't know if that is even possible..
I'm also open to any alternative methods, as long as I can get the process name (to check against a pre-defined list of processes, and the full path to the exe, plus any command line paramteres given.
Thanks all
In direct answer to you question: No, you cannot specify 2 spaces as a delimiter. You can use SET search and replace to change 2 spaces into some unique character, but determining a unique character that will never appear in your description or command line is easier said then done.
A better alternative is to change the output format of WMIC to LIST - one value per line in the form of propertyName=Value. Each propery value can be stored in a variable, and then when the last property for a process is recorded you can take action using the variable values. WMIC output uses Unicode, and that results in a CarriageReturn character being appended to the end of each variable assignment. The CarriageReturn must be stripped to get the correct results.
#echo off
setlocal
for /f "tokens=1* delims==" %%A in ('"wmic process get description, commandline /format:list"') do (
if "%%A"=="CommandLine" (
set "cmd=%%B"
) else if "%%A"=="Description" (
set "desc=%%B"
setlocal enableDelayedExpansion
set "desc=!desc:~0,-1!"
set "cmd=!cmd:~0,-1!"
echo(
echo Do whatever you need to do with the description and command line.
echo description=!desc!
echo command line=!cmd!
endlocal
)
)
There are a few things you need to be careful of.
1) You could have multiple processes for the same image name. If you kill a process via the image name (description), then you will delete all of them. If you also restart it it based on the command line, then it will be killed again when the next process with the same name is killed. It is probably better to kill the process via the process ID.
2) If you know the image name (description) of the process, then you can restrict your output using the WMIC WHERE clause.
3) The command line reported by WMIC is not always reliable. The process is able to modify the value that is reported as the command line.
Here is a solution that retrieves the process ID and command line for a specific description.
EDIT - I fixed the code below
#echo off
setlocal
for /f "tokens=1* delims==" %%A in ('"wmic process where description='MyApp.exe' get processId, commandline /format:list"') do (
if "%%A"=="CommandLine" (
set "cmd=%%B"
) else if "%%A"=="ProcessId" (
set "id=%%B"
setlocal enableDelayedExpansion
set "id=!id:~0,-1!"
set "cmd=!cmd:~0,-1!"
echo(
echo Do whatever you need to do with the process id and command line.
echo process Id=!id!
echo command line=!cmd!
endlocal
)
)
Note - the WMIC WHERE clause uses SQL syntax. It can be made complex using AND and OR conditions, and it supports the LIKE operator using % and _ as wildcards. I believe the entire expression needs to be enclosed in double quotes when it becomes complex.
Foreword
I'd just like to add this for future readers, because I had this problem, solved it myself and I think it'll be useful just to show how to do this simply. Firstly, dbenham is absolutely correct in his answer that "No, you cannot specify 2 spaces as a delimiter.". Since you can't do it directly using the batch for loop, you can simply make your own that does the job. Again dbenham is correct in saying
"You can use SET search and replace to change 2 spaces into some unique character"
And thats somewhat similar to what I did (with some differences) but for completeness sake I think its good to have it on record. The thing is, simply setting all occurences of double spaces to some other character doesn't always solve the problem. Sometimes we have more than two spaces and what we really want is to delimit strings by more than one space. The problem I was trying to solve here is more like this (from the OP) Ricky Payne
"Another way that may be better could be to replcae all instances of multiple spaces with a special character (i.e. one that is never used
in a proces or path), and then use that as my delimeter... Though I
don't know if that is even possible.."
The answer to that is that It IS possible, and not hard at all. All you need is to be able to
A. loop over each character of the string
B. differentiate single from double (or more) spaces
C. turn a flag on when you encounter a double space
D. turn the double (or more) spaces into a special character or sequence of characters that you can delimit by.
the code
To do Exactly this, I coded this for my own use (edited for clarity):
FOR /F "tokens=* delims=*" %%G IN ('<command with one line output>') DO (SET
"LineString=%%G")
SET /A "tempindex=0"
:LineStringFOR
SET "currchar=!LineString:~%tempindex%,1!"
IF "!currchar!"=="" (goto :LineStringFOREND)
SET /A "tempindex=!tempindex!+1"
SET /A "BeforeSpacePosition=!tempindex!"
SET /A "AfterSpacePosition=!tempindex!+1"
IF NOT "!LineString:~%BeforeSpacePosition%,2!"==" " (goto :LineStringFOR)
:LineStringSUBFOR
IF "!LineString:~%BeforeSpacePosition%,2!"==" " (
SET LineString=!LineString:~0,%BeforeSpacePosition%!!LineString:~%AfterSpacePosition%!
GOTO :LineStringSUBFOR
) ELSE (
SET LineString=!LineString:~0,%BeforeSpacePosition%!;!LineString:~%AfterSpacePosition%!
GOTO :LineStringSUBFOREND
)
:LineStringSUBFOREND
GOTO :LineStringFOR
:LineStringFOREND
ECHO Final Result is "!LineString!"
So if your input (output of the command in the FOR or you can change that FOR loop to take in a string) was:
"a b c a b c"
The output should be in this format:
"a;b;c;a b c"
I have tested this on my own code. However, for my answer here I removed all of my comments and changed some variable names for clarity. If this code doesn't work after putting in your commands feel free to let me know and I'll update it but it SHOULD be working. Formatting on here might prevent a direct copy paste.
Just to show whats actually going on
The program flow is basically like this:
FOR each character
:TOP
grab the next character
set a variable to the current index
set another variable to the next index
IF this or the next character are not spaces, goto the TOP
:Check for 2 spaces again
IF this and the next character are both spaces then
get the string up to (but not including) the current index AS A
get the string after the current index AS B
set the string to A+B
goto Check for 2 spaces again
ELSE we have turned the double or more space into one space
get the string up to (but not including) the current index AS A
get the string after the current index AS B
set the string to A + <char sequence of choice for delimiting> + B
goto TOP to grab the next character
After all characters are looped over
RETURN the string here (or echo it out like I did)
Extra
dbenham says in his answer on this type of method that:
"You can use SET search and replace to change 2 spaces into some
unique character, but determining a unique character that will never
appear in your description or command line is easier said then done."
While this may have been true in the past, my yeilded that (at least for my method correct me if I'm wrong for other cases) you can in fact use a delimiter that definitely WON'T appear in your input. this is accomplished by using multicharacter delimiters. This doesn't allow you to use the standard FOR loop, however you can quite easily do this manually. This is described much more in depth here:
"delims=#+#" - more then 1 character as delimiter
Great thread!
This got me thinking and I came up with a slightly sideways solution that may work well for someone as it did for me.
As the original questions was for the WMIC command, and the output can be CSV format, why not just circumvent the space handling by using the /format:csv switch and setting a comma as the delimiter, and incorporating 'usebackq'?
Of course, this might not work if the data itself from WMIC has commas but waqs perfect in my instance where I wanted only the BootOptionOnWatchDog status
would look something like this:
FOR /F "usebackq skip=1 tokens=1-31 delims=," %a IN (`%windir%\system32\wbem\wmic computersystem list /format:csv`) DO echo %f
which returns:
BootOptionOnWatchDog
Normal boot
I ended using 'skip=2' which would return "Normal Boot"
btw, dont post here often hence posting as a guest, but thought it prudent to put this here as it was this post that helped me come to the answer above.
cheers
-steve (NZ)

Resources