Batch file - How to find quotes in a string? - windows

When given an environment variable that may or may not be quoted I need to remove the quotes.
For example, I may be given:
JAVA_HOME=C:\Program Files\Java\jdk1.8.0_144
JAVA_HOME="C:\Program Files\Java\jdk1.8.0_144"
I am trying to use findstr and it seems to work well from cmd.exe but not from my batch file. This is what I have in test1.bat:
#echo off
echo.%JAVA_HOME% | findstr \^" 1>nul
if ERRORLEVEL 0 (
echo Found Quotes
)
if ERRORLEVEL 1 (
echo No Quotes found
)
The problem I have is that the ERRORLEVEL always seems to be 0 and I always get the message "Found Quotes" when I run test1.bat.
I have this code to remove quotes for /f "delims=" %%G IN (%JAVA_HOME%) DO SET JAVA_HOME=%%G which works find, but only when there are quotes, hence needing to get the above findstr conditions working correctly.

Batch files already have the ability to remove quotes on for-loop variables like %%G in your example, so you do not need any of that findstr logic. The reason why your for doesn't work is because you're not quoting the %JAVA_HOME%. You always have to quote arguments that have spaces.
From help for, the syntax is:
FOR /F ["options"] %variable IN ("string") DO command
[command-parameters]
And on automatic variables like %%G, the tilde operator removes quotes if they are present.
From help for:
In addition, substitution of FOR variable references has been
enhanced. You can now use the following optional syntax:
%~I - expands %I removing any surrounding quotes (")
So with these 2 pieces of information, change your for loop to this to remove quotes from %JAVA_HOME%.
for /f "delims=" %%G IN ("%JAVA_HOME%") DO SET "JAVA_HOME=%%~G"

You typically have to test for other values for ERRORLEVEL before testing for 0, IIRC. This works perfectly for me:
#echo off
echo.%JAVA_HOME% | findstr \^" 1>nul
if ERRORLEVEL 1 (
echo No quotes found
goto :eof
)
if ERRORLEVEL 0 (
echo Found quotes
)
NOTE: Removing the goto :eof means you get both lines output by echo if there is no match.

#ECHO OFF
SETLOCAL
SET JAVA_HOME=C:\Program Files\Java\jdk1.8.0_144
SET "java_home=%java_home:"=%"
SET j
ECHO -------------------------------------------
SET JAVA_HOME="C:\Program Files\Java\jdk1.8.0_144"
SET "java_home=%java_home:"=%"
SET j
GOTO :EOF
Either way, java_home ends up "nude".

Related

Batch renaming files incrementally with special characters

I am trying to rename the episodes in a directory in an incremental way, but there are exclamation marks in some of the episodes. It will skip those files. I tried doing delayed expansion, but it didn't work.
#echo off
setlocal DisableDelayedExpansion
set /a num=0
for %%a in (*.mkv) do (
set filename=%%a
setlocal EnableDelayedExpansion
ren "!filename!" "Soul Eater Episode 0!num!.mkv"
set /a num=!num!+1
)
pause
endlocal
Try this:
#echo off
setlocal EnableDelayedExpansion
set /a num=0
for %%a in (*.mkv) do (
set filename=%%a
ren "!filename!" "Soul Eater Episode 0!num!.mkv"
set /a num=!num!+1
)
endlocal
pause
The following batch file code could be used to rename the files containing one or more ! in file name.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "num=0"
for /F "eol=| delims=" %%I in ('dir *.mkv /A-D /B /ON 2^>nul ^| %SystemRoot%\System32\findstr.exe /B /I /L /V /C:"Soul Eater Episode"') do (
set "filename=%%I"
setlocal EnableDelayedExpansion
ren "!filename!" "Soul Eater Episode 0!num!.mkv"
endlocal
set /A num+=1
)
pause
endlocal
There is no need to use an arithmetic expression to define the environment variable num with the value 0.
It is very advisable on running renames on a list of file names in a directory using a wildcard pattern like *.mkv to get first the list of file names loaded into memory of Windows command processor and then rename one file after the other as done by this code using a for /F loop. Otherwise the result of the file renames is unpredictable as depending on file system (NTFS, FAT32, exFAT) and current names of the files matched by the wildcard pattern.
The additional FINDSTR is used to filter out all file names beginning already case-insensitive with the string Soul Eater Episode although the batch file would most likely fail to rename some files if there are already files with a file name matched by Soul Eater Episode 0*.mkv in the current directory on execution of the batch file.
Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul and |. The redirection operators > and | must be escaped with caret character ^ on FOR command line to be interpreted as literal characters when Windows command interpreter processes this command line before executing command FOR which executes the embedded command line with dir and findstr with using a separate command process started in background with %ComSpec% /c and the specified command line appended as additional arguments.
The file name is first assigned as output by DIR filtered by FINDSTR to the environment variable filename with delayed expansion disabled as otherwise the double processing of this command line on enabled delayed expansion would result in interpreting ! in file name assigned to loop variable I as beginning/end of a delayed expanded environment variable reference.
Then delayed expansion is enabled to be able to do the rename with referencing the environment variable num using delayed expansion and of course also the file name assigned to environment variable filename.
Next delayed expansion is disabled again before an arithmetic expression is used using the preferred syntax to increment the value of an environment variable by one which always works independent on disabled or enabled delayed expansion.
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.
dir /?
echo /?
endlocal /?
findstr /?
for /?
pause /?
ren /?
set /?
setlocal /?
Please read this answer with details on what happens in background on every execution of SETLOCAL and ENDLOCAL.

Double quotes in delims=?

I'm very new to batch scripting, but in my last question I was trying to extract a link from a line of text, specifically:
83: href="https://beepbeep.maxresdefault.rar"><img
What I want out of it is this:
https://beepbeep.maxresdefault.rar
Someone suggested using for /f to separate the string, and I'd like to separate it every " mark, taking only the second token, as what I want is trapped between the two "".
Here is what I've got:
for /f "delims=^" tokens=^2" %%G in (output2.txt) do #echo %%G %%H >> output3.txt
The batch crashes at this point, I'm guessing it's the wrong syntax, but I'm not sure where the issue is, maybe in the " part?
See how we delimit on double quotes, without surrounding quotes. We have already assigned the variable between the quotes to %%a but if we did not, then to remove the double quotes from the string we expand the variable %%a to %%~a (see for /? on variable expansion):
#for /f delims^=^"^ tokens^=2 %%a in (output2.txt) do #echo %%~a
Neat problem.
I'd do it this way:
SETLOCAL ENABLEDELAYEDEXPANSION
for /F "tokens=2 delims=^>=" %%i in (output2.txt) do (
set x=%%i
set x=!x:"=!
echo !x! >> output3.txt
)
Notes:
Instead of tokenising on the quote, I've tokenised on = (before) and > (after). Because, as you already know, quotes are hard
I always do the delims last. Otherwise it might think the space between delims and tokens is a delimeter.
Then it uses the SET syntax that allows you to substitute one character for another to replace all occurances of the double quote with nothing.
The SETLOCAL ENABLEDELAYEDEXPANSION is necessary because otherwise, each evaluation of the %x% in the loop uses the original value of %x% which is probably wrong. I always have this as the second line in my batch file.
Judging by how much you've already got, I'm guessing you've seen it, but if you haven't, I've found ss64.com to be the best resource.
https://ss64.com/nt/syntax-dequote.html

Batch file mangling a string with special chars and quotes inside a FOR loop

This code snippet below is stripped of all the extra junk, down to just the error-generating code,
)"" was unexpected at this time.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS
Set "regex="(Test_Health=(?!100))""
echo Regex is: %regex%
FOR /L %%I IN (1,1,5) DO (
Set "to_call=call crv.exe "%%I" %regex%"
echo About to call: !to_call!
)
Basically, in the real script, I'm trying to call a command-line tool that takes a complex string with potentially special chars in it, as well as a regex.
I figured out a workaround, which was to add a single caret (^) before %%I's last quote:
Set "to_call=call crv.exe "%%I^" %regex%"
But that feels like a dirty hack. What am I doing wrong, and what should I do to get the desired behavior without a dirty hack?
To fix your problem without a hack:
Make sure that the ! char. in your regex variable value is recognized as a literal:
Set "regex=(Test_Health=(?^!100))"
Due to setlocal enabledelayedexpansion, literal ! chars. inside "..." must be escaped as ^!.
Note that the <name>=<value> token is double-quoted as a whole, to prevent additional interpretation of the value.
Reference variable regex delayed inside the for loop body:
Use !regex! instead of %regex%.
To make the resulting command line more robust - even though it's not needed in this specific case - ensure that the value of regex is enclosed in double quotes (note that %%I - as a mere number - does not need quoting):
Set "to_call=call crv.exe %%I "!regex!""
To put it all together:
#echo off
setlocal enabledelayedexpansion enableextensions
Set "regex=(Test_Health=(?^!100))"
echo Regex is: %regex%
FOR /L %%I IN (1,1,5) DO (
Set "to_call=call crv.exe %%I "!regex!""
echo About to call: !to_call!
)
yields:
Regex is: (Test_Health=(?100))
call crv.exe 1 "(Test_Health=(?!100))"
call crv.exe 2 "(Test_Health=(?!100))"
call crv.exe 3 "(Test_Health=(?!100))"
call crv.exe 4 "(Test_Health=(?!100))"
call crv.exe 5 "(Test_Health=(?!100))"
As for what you did wrong:
%<name>%-style variable references - except for the loop variable (%%I in this case) - are expanded inside the loop body ((...)) before the loop is even parsed, so the values of such variable references can break the loop.
Here's a minimal example that demonstrates the problem:
#echo off
Set "regex=))"
FOR %%I IN ("dummy") DO (
rem !! breaks, because the up-front %regex% expansion causes a syntax error.
echo %regex%
)
Delaying the expansion - by enclosing the variable name in !...!, assuming setlocal enabledelayedexpansion is in effect - bypasses this problem:
#echo off
setlocal enabledelayedexpansion
Set "regex=))"
FOR %%I IN ("dummy") DO (
rem OK - outputs "))"
echo !regex!
)
#ECHO OFF
SETLOCAL
Set "regex="(Test_Health=(?^^^^!100^^)^^)""
SETLOCAL ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS
echo Regex is: %regex%
FOR /L %%I IN (1,1,5) DO (
Set "to_call=call crv.exe "%%I" %regex%"
echo About to call: !to_call!
)
GOTO :EOF
Sort of depends on what your "desired behaviour" is. Unfortunately, you don't specify.
It's a matter of understanding how cmd works - by substitution, using escape characters and the sequence that this occurs.
The echo reporting the regex won't yield the correct result. Within the for however, each pair of carets is interpreted as a single caret, so the required escapes are as required for the expected output, presumably call crv.exe "5" "(Test_Health=(?!100))" and the like...

Batch For Loop: Use wildcard character in string

I have been translating some shell code to MS-DOS Batch. In my code, I have the sample:
for %%i in (%*) do set "clargs=!clargs! %%i"
If I input the argument "-?" (without the quotation marks), it is not added to clargs. I assume it is because '?' is a wildcard character. Is there anything I can do to ensure that for does not do special things because of the question mark being located in the argument?
You are correct, the wild card characters * and ? are always expanded when used within a FOR IN() clause. Unfortunately, there is no way to prevent the wildcard expansion.
You cannot use a FOR loop to access all parameters if they contain wildcards. Instead, you should use a GOTO loop, along with the SHIFT command.
set clargs=%1
:parmLoop
if "%~1" neq "" (
set clargs=%clargs% %1
shift /1
goto :parmLoop
)
Although your sample is quite silly, since the resultant clargs variable ends up containing the same set of values that were already in %*. If you simply want to set a variable containing all values, simply use set clargs=%*
More typically, an "array" of argument variables is created.
set argCnt=0
:parmLoop
if "%~1" equ "" goto :parmsDone
set /a argCnt+=1
set arg%argCnt%=%1
shift /1
goto :parmLoop
:parmsDone
:: Working with the "array" of arguments is best done with delayed expansion
setlocal enableDelayedExpansion
for /l %%N in (1 1 %argCnt%) do echo arg%%N = !arg%%N!
See Windows Bat file optional argument parsing for a robust method to process unix style arguments passed to a Windows batch script.
#ECHO OFF
SETLOCAL
SET dummy=%*
FOR /f "tokens=1*delims==" %%f IN ('set dummy') DO CALL :addme %%g
ECHO %clargs%
GOTO :eof
:addme
IF "%~1"=="" GOTO :EOF
IF DEFINED clargs SET clargs=%clargs% %1
IF NOT DEFINED clargs SET clargs=%1
SHIFT
GOTO addme
I severly doubt you'll get a completely bullet-proof solution. The above solution will drop separators (comma, semicolon, equals) for instance. Other solutions may have problems with close-parentheses; there's the perpetual % and ^ problems - but it will handle -?
But for your purposes, from what you've shown, what's wrong with
set clargs=%clargs% %*
(No doubt you'll want to process further, but ve haff vays...)

Windows Shell Scripting: Check for batch options containing double quotes

Greetings, dear Experts!
I want to check the existense of parameter (or argument) sting at all in my batch script:
if "%*"=="" findstr "^::" "%~f0"&goto :eof
This works fine if none of parameters is enclosed in double quotes. For example:
test.bat par1 par2 ... --- works
but
test.bat "par 1" par2 ... --- fails
My question are:
1) Is there any way to overcome this instead of requirement for use to use non-double-quoted symbol to specify "long" arguments and then use string substitution?
2) Can I ever use "if" to compare two strings containing both double quotes and spaces?
Your prompt and clear reply would be very much appreciated.
~ will strip the double quotes, but does not work on %*, but if you just want to know if there are no parameters, just checking %1 should be enough
if "%~1"==""
You might want to call setlocal ENABLEEXTENSIONS first to make sure extensions are on (required for ~)
Thank you, Andres.
Here are two more pieces of code to check the existence and number of passed parameters:
set ArgumentString=%*
if not defined ArgumentString findstr "^::" "%~f0"&goto :eof
if "%ArgumentString:"=%"=="" findstr "^::" "%~f0"&goto :eof
set NumberOfArguments=0
for /l %%N in (1,1,9) do (
call set CurrentArgument=%%%%N
if defined CurrentArgument set /a NumberOfArguments += 1
)
if %NumberOfArguments% NEQ %N% findstr "^::" "%~f0"&goto :eof
Here variable N contains needed number of parameters.
Hope this helps for somebody!
Since Andrey's answer fails to correctly count arguments, here is a way that does work, even with more than 9 arguments (and it even perserves the original ones, so %1 still points to the first one by moving the shifting into a subroutine):
#echo off
call :get_num_args %*
echo %NumArgs%
goto :eof
:get_num_args
set NumArgs=0
:loop
if not [%1]==[] (set /a NumArgs+=1) else (goto :eof)
shift
goto loop
goto :eof

Resources