Batch file - extract value with delimiter from result containing spaces - windows

I saw similar questions but none is related to my issue.
I am using regedit to retrieve result where python is installed. I want to get this value:C:\Program Files\Python38\python.exe
C:\Users\user1>reg query HKLM\SOFTWARE\Python\PythonCore\3.8\InstallPath /V ExecutablePath
HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\3.8\InstallPath
ExecutablePath REG_SZ C:\Program Files\Python38\python.exe
Now I wrote a script:
SET PYTHON_PATH_SEARCH=reg query HKLM\SOFTWARE\Python\PythonCore\3.8\InstallPath /V ExecutablePath
FOR /F "tokens=1,2,3 delims= " %%A IN ('%PYTHON_PATH_SEARCH%') DO (
CALL :set_python_path %%C
)
:set_python_path
SET PYTHON_PATH=%1
exit /B 0
But the result is: C:\Program
The issue is that I am delimiting it using the spaces but I have a space in my desired output.
Thanks in advance~

Use tokens=1,2,*
Token * is the remainder, after the highest nominated token number
AND call :set_... "%%C" Then %~1 in the subroutine.
Quoting combines a space-infused string into a single string.
~ removes the outer set of quotes from a metavariable.
Use set "var=value" for setting string values - this avoids problems caused by trailing spaces. Don't assign " or a terminal \ or Space. Build pathnames from the elements - counterintuitively, it is likely to make the process easier

Related

Delete smaller resolutions of an image based on file name

I am looking to automatically clean directories that contain original photos and smaller resolutions of a single photo.
I have the following structure for file names
original_image.jpg
original_image-1024x768.jpg
original_image-800x600.jpg
original_image-640x480.jpg
Is there a way, using a windows script (cmd, not PowerShell) to look through files in a directory, and delete any files that has the same name followed by a dash, a group of digits, and x, another group of digits, then the samme extension as the original file?
OOh - not that easy! Be careful!
#ECHO OFF
SETLOCAL enabledelayedexpansion
rem The following setting for the source directory is a name
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
FOR /f "delims=" %%b IN (
'dir /b /a-d "%sourcedir%\*" ^|findstr /i /v /r ".*-[0-9]*x[0-9]*.*" '
) DO (
SET "filter=%%~nb-[0-9]*x[0-9]*\%%~xb"
SET "filter=!filter: =\ !"
FOR /f "delims=" %%e IN (
'dir /b /a-d "%sourcedir%\%%~nb*%%~xb" ^|findstr /i /r "!filter!" '
) DO ECHO DEL "%%e"
)
GOTO :EOF
Always verify against a test directory before applying to real data.
The required DEL commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO DEL to DEL to actually delete the files.
The outer loop (%%b) processes a 'dir' list of the directory /a-d without directorynames and /b in basic form (names only - no headers. footers or details.)
The list is passed to a findstr command to filter the filename pattern required. The pipe | must be escaped by a caret^ to tell cmd that the pipe belongs to the single-quoted command to be executed, not to the for.
The findstr filter is /i case-insensitive /r using a regular expression. The /v option outputs those lines that do not match the filter. The regular expression is .* any number of any character, - -literal dash [0-9]* any number of numeric characters x literal "x" [0-9]* any numerics again and .* any characters.
The delims= causes the filenames to be delivered literally to %%b by setting no delimiters and hence just one token. See for /? from the prompt or endless examples on SO for documentation.
Next step is to set up the filter for the next findstr. This is %%~nb the name part of the filename in %%b and %%~xb the extension part (including the dot). The \ escapes the dot contributed by %%~xb, making it a literal dot instead of a single-character-match.
The next step replaces each "space" with "<kbd>space" See set /? from the prompt or endless examples on SO for documentation.
Finally, execute another dir but this time, look for files matching the pattern of the filename and the extension of %%b, separated by anything and filtering using the string established in filter.
delayedexpansion is required since filter is being changed within a code block (parenthesised sequence of lines) - so !var! retrieves the current value of the variable where %var% is the original vale (when the block was encountered).
Why the extra complexity?
Suppose the file list includes
original_image.jpg
original_image-1024x768.jpg
original_image-800x600.jpg
"original image.jpg"
"original image-1024x768.jpg"
"original image-800x600.jpg"
Then because a Space in a findstr causes an or of the strings before and after the space, so whereas the %%e dir selects on those files matching "original image*.jpg", this includes "original image.jpg". The regex constructed would be "original image-[0-9]*x[0-9]*\.jpg" which matches "original" and therefore "original image.jpg" will be selected for deletion.

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! :)

How can I get a string between two quotes in a batch file?

I have a string in a batch file, of the structure
[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}
I need to get just the 01bcd123-1234-5678-0000-abcdefghijkl out of it, but trying to use " as a delimiter doesn't turn out well. \ and ^ don't seem to escape it properly.
set i=1
set "x!i!=%x:"=" & set /A i+=1 & set "x!i!=%"
Is what I have with x being the whole string, attempting to parse it into x1, x2 etc with " as the delimiter.
What is a proper way to split this string, using " as the delimiter?
Edit: Powershell tag is because I am running the script as part of a larger orchestration in Powershell and could export the functionality of the batch script into it if necessary.
Here are two approaches. The first one doesn't mess with the for syntax format, but it's risky - too much dependence on the string (the quotes are actually stripped by %%~). The second one is an ugly non-intuitive syntax, but actually delimits by quotes:
set "string=[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}"
for /f "tokens=2 delims=:{" %%a in ("%string%") do #echo %%~a
for /f tokens^=2delims^=^" %%a in ("%string%") do #echo %%a
Well, the self-expanding code you have posted works fine, given that you have got delayed expansion enabled, by having put the statement setlocal EnableDelayedExpansion placed before. The string of interest is then stored in variable x2. Note that when the script terminates, x2 (like all the other x# variables as well) is no longer available since an implicit endlocal is executed then. To avoid that, place endlocal & set "x2=%x2%" in the last line:
#echo off
rem // Define string to parse:
set "x=[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}"
rem // Enable delayed expansion:
setlocal EnableDelayedExpansion
rem // Initialise index counter:
set i=1
rem // Split string using self-expanding code:
set "x!i!=%x:"=" & set /A i+=1 & set "x!i!=%" & rem // (unbalanced `"`!)
rem // Display all `x#` variables:
set x
rem // Make `x2` survive the `endlocal` barrier:
endlocal & set "x2=%x2%"
rem // Return the retrieved value:
echo(%x2%
However, I would most probably use a for /F loop, but not with " as delimiter since the syntax appears quite odd then; rather I would use :, {, } and SPACE as delimiters. But I would remove the prefix [[status]] in advance:
#echo off
rem // Define string to parse:
set "x=[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}"
rem /* At first, split off everything up to the first occurrence of `]]`;
rem if there is no such prefix, there is no harm, because nothing happens;
rem then extract the first token that is delimited by `:`, `{`, `}` or space;
rem that way there may even be spaces around the `:` or around `{` or `}`;
rem then return it with surrounding quotation marks removed (`~`-modifier): */
for /F "tokens=1 eol=: delims=:{} " %%I in ("%x:*]]=%") do echo(%%~I
N. B.:
The odd-looking syntax echo( is not a typo, it is actually the only safe way to echo an arbitrary string (even on, off or /?); take a look at this external thread for more details.
Since you tagged PowerShell, you can use the following regex, but I am not sure you want PowerShell based on the question.
[regex]::Match('[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}','(?<=")[^"]+(?=")').Value
Split regex can also work:
('[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}' -split '"')[1]
If you stick with a batch file, Stephan's helpful answer is definitely the simplest and fastest solution.
Needless to say, if you port your batch file to PowerShell, you'll have vastly more functionality at your disposal.
You can even harness that functionality from a batch file via PowerShell's CLI, by calling powershell.exe (Windows PowerShell) or pwsh.exe (POwerShell Core), but that comes with two caveats:
Doing so creates a PowerShell child process, whose startup time is not insignificant.
Getting nested quoting right can be a challenge, as shown below.
Here's a solution that calls PowerShell's CLI from a batch file, applying the -split technique from AdminOfThings' helfpul answer; again, this solution would be overkill in the case at hand, but the approach may be of interest if you need to perform tasks that simply cannot be done in the batch language or would be too cumbersome.
#echo off
setlocal
:: # The input text.
set txt=[[status]]:{"01bcd123-1234-5678-0000-abcdefghijkl": "11"}
:: # Call the PowerShell CLI to extract the token of interest and save the
:: # result in variable %id%.
:: # In PowerShell code, the equivalent would be:
:: # $id = ($txt -split '"')[1]
for /f %%i in ('powershell -noprofile -c "('%txt:"=\"%' -split '\""')[1]"') do set id=%%i
:: # Echo the result.
echo %id%
Note the need to \-escape the " chars. embedded in %txt%, via substitution %txt:"=\"%, and the need for an additional " char. after \" in '\""' so as to prevent the for command from breaking.

How to get file version of a DLL or EXE to a variable using WMIC in a Windows batch file?

I try to get the file version using a Windows batch file. This command successfully prints the version to console.
WMIC DATAFILE WHERE name="Z:\\bin\\My_project.dll" get Version /format:Textvaluelist
But when I try to get this output to a variable using the below method, Windows command processor outputs:
Z:\\bin\\My_project.dll - Invalid alias verb.
What is wrong on this command line?
for /f %%a in ('WMIC DATAFILE WHERE name="Z:\\bin\\My_project.dll" get Version /format:Textvaluelist') do set val=%%a
This is how I'd attempt this task using WMIC:
For /F "Tokens=1* Delims==" %%A In (
'WMIC DataFile Where "Name='Z:\\bin\\My_project.dll'" Get Version /Value 2^>Nul'
) Do For /F "Tokens=*" %%C In ("%%B") Do Set "val=%%C"
The second For loop is designed to overcome the unwanted output resulting from the unicode WMIC result.
Edit
If WMIC wasn't stipulated, you could utilise VBScript from your batch file:
Using Windows Scripting Host:
<!-- :
#Echo Off
For /F %%A In ('CScript //NoLogo "%~f0?.wsf"') Do Set "val=%%A"
Set val 2>Nul
Pause
Exit /B
-->
<Job><Script Language="VBScript">
WScript.Echo CreateObject("Scripting.FileSystemObject").GetFileVersion("Z:\bin\My_project.dll")
</Script></Job>
Or via mshta.exe:
#Echo Off
For /F %%A In ('
MsHTA VBScript:Execute("Set o=CreateObject(""Scripting.FileSystemObject""):o.GetStandardStream(1).Write(o.GetFileVersion(""Z:\bin\My_project.dll"")):Close"^)
') Do Set "val=%%A"
Set val 2>Nul
Pause
The problem is the equal-to sign in your where clause, which is seen as a standard token separator by cmd, just like SPACE, TAB, ,, ;, the vertical tab (code 0x0B), the form-feed (code 0x0C) and the non-break space (code 0xFF).
The for /F command executes the command to be parsed in a separate cmd instance, but before doing that, every sequence of standard token separators becomes replaced by a single SPACE. So also the = becomes replaced, which leaves some odd syntax behind.
To literally keep the =-sign you need to escape it like ^=. An alternative way is to change quotation style so that the = appears in between "", by stating wmic DataFile where "Name='Z:\\bin\\My_project.dll'" get Version.
But this still does not deliver the expected result, because wmic returns Unicode output, which leaves some disturbing conversion artefacts like orphaned carriage-return characters behind. This can be avoided by nesting another for /F loop.
Additionally, because of your output chosen format, you would also get a Version= prefix, which is probably not what you want to be included. This can be changed by defining suitable tokens and delims options for for /F.
All these items lead to a code like the following:
for /F "usebackq delims=" %%a in (`
wmic DataFile where "Name='Z:\\bin\\My_project.dll'" get Version /VALUE
`) do for /F "tokens=1* delims==" %%b in ("%%a") do set "val=%%c"
This would remove any leading =-signs from the returned version, if there were any.
The simplest solution to get version of a DLL is using this command line in a batch file:
for /F "tokens=2 delims==" %%I in ('%SystemRoot%\System32\wbem\wmic.exe DATAFILE WHERE name^="Z:\\bin\\My_project.dll" get Version /VALUE 2^>nul') do set "val=%%I"
Also possible is this slightly different command line in a batch file:
for /F "usebackq tokens=2 delims==" %%I in (`%SystemRoot%\System32\wbem\wmic.exe DATAFILE WHERE "name='Z:\\bin\\My_project.dll'" GET Version /VALUE 2^>nul`) do set "val=%%I"
FOR runs the command in the round brackets using a separate command process started with %ComSpec% /c in the background and captures all lines output to STDOUT of this command process. For that reason it must be taken into account how cmd.exe instance currently processing the batch file parses the entire command line and how FOR processes the argument strings within the round brackets.
A redirection operator like > outside a double quoted string must be escaped with ^ to get it interpreted first as literal character when Windows command processor parses the command line before executing FOR.
And an equal sign = outside a double quoted string is interpreted by FOR as an argument separator like a space or a comma as it can be seen on running in a command prompt window for example:
for %I in ("space ' ' and comma ',' and equal sign '='" space,comma=equal_sign) do #echo %I
This command line outputs:
"space ' ' and comma ',' and equal sign '='"
space
comma
equal_sign
For that reason also = outside a double quoted string must be escaped with ^ to be interpreted first as literal character.
The second solution uses an alternate WMIC syntax to embed the equal sign in a double quoted string and a different FOR syntax using back quotes to be able to use ' in command line to execute by FOR.
WMIC outputs with option /VALUE or /FORMAT:Textvaluelist for example:
 
 
Version=1.0.0.11
 
 
So the WMIC output consists of two empty lines, the queried information Version with an equal sign and the version value, and two more empty lines.
The problem is that WMIC outputs always the queried data using UTF-16 Little Endian character encoding with byte order mark (BOM) and FOR has a bug on processing this Unicode output correct. FOR interprets a byte sequence with the hexadecimal values 0D 00 0A 00 being UTF-16 LE encoded the line ending carriage return + line-feed as 0D 0D 0A. So there is an erroneous carriage return at end of the real line ending removed by FOR before processing further the remaining string.
For details about this FOR bug see for example:
How to correct variable overwriting misbehavior when parsing output?
cmd is somehow writing Chinese text as output
This bug is worked around here partly by using option /VALUE to get the wanted information output by WMIC on one line in format Version=value and using FOR options tokens=2 delims==. Only the captured line with Version=value has two strings separated by an equal sign and so the command SET is executed only on this line with the value after the equal sign. The two empty lines before and the two empty lines after line with version information, wrong interpreted by FOR as lines with just a carriage return, do not result in executing command SET as there is no second string which could be assigned to loop variable I.
It is expected by this code that the version string does not contain itself one or more = which should be always the case for version of a DLL.
Note 1: The value of environment variable val is unmodified respectively this environment variable is still not defined if any error occurs like the DLL file is not found at all. The error message output by WMIC is suppressed by redirecting it with 2>nul (with escaping >) from STDERR to device NUL.
Note 2: The version string assigned to the environment variable val has as last character the erroneous carriage return. That must be taken into account on further processing the string value of val. The solutions of Compo and aschipfl using two FOR loops as posted are better because the environment variable val is defined with the version string of the DLL without the erroneous carriage return.

Batch adding a character every x characters

If I get my parameter with %1 and it is "Server" how can I add a + sign after every letter?
So my result would be "S+e+r+v+e+r"?
I think Batch file to add characters to beginning and end of each line in txt file this is a similar question but I don't know how to change the code for this purpose.
Any help would be great!
I'm pretty sure this has been asked and answered before, but I couldn't find it.
There is a really cool (and fast) solution that I saw posted somewhere. It uses a new cmd.exe process with the /U option so output is in unicode. The interesting thing about the unicode is that each ASCII character is represented as itself followed by a nul byte (0x00). When this is piped to MORE, it converts the nul bytes into newlines!. Then a FOR /F is used to iterate each of the characters and build the desired string. A final substring operation is used to remove the extra + from the front.
I tweaked my memory of the code a bit, playing games with escape sequences in order to get the delayed expansion to occur at the correct time, and to protect the character when it is appended - all to get the technique to preserve ^ and ! characters. This may be a new twist to existing posted codes using this general technique.
#echo off
setlocal enableDelayedExpansion
set "str=Server bang^! caret^^"
set "out="
for /f delims^=^ eol^= %%A in ('cmd /u /v:on /c echo(^^!str^^!^|more') do set "out=!out!+^%%A"
set "out=!out:~1!"
echo Before: !str!
echo After: !out!
--OUTPUT---
Before: Server bang! caret^
After: S+e+r+v+e+r+ +b+a+n+g+!+ +c+a+r+e+t+^
This batch file should do it:
#ECHO OFF
SETLOCAL EnableDelayedExpansion
SET Text=%~1
SET Return=
REM Batch files don't have a LEN function.
REM So this loop will process up to 100 chars by doing a substring on each.
FOR /L %%I IN (0,1,100) DO (
CALL SET Letter=!Text:~%%I,1!
REM Only process when a letter is returned.
IF NOT "!Letter!" == "" (
SET Return=!Return!+!Letter!
) ELSE (
REM Otherwise, we have reached the end.
GOTO DoneProcessing
)
)
:DoneProcessing
REM Remove leading char.
SET Return=%Return:~1,999%
ECHO %Return%
ENDLOCAL
Calling with Test.bat Server prints S+e+r+v+e+r to the console.

Resources