Find a substring (filepath) from a string containing variable order arguments - windows

%params% contains a variable set of arguments:
/tidy /log /truncate /convert D:\libdir
or maybe
/log /tidy D:\cyclea\libfolder /test /convert /truncate
for everything but the (currently single) filepath element I use it such:
if "%params%"=="%params:log=%" goto :DontLogit
if NOT "%params%"=="%params:/tidy=%" (call tidysub: & do something else )
Now I want to extract the filepath element and use it as an argument to a command eg chdir
I've played with, but I'm weak with CMD string manipulation and for loops.
I'd like to keep the order of params variable.
For info it comes from here:
FOR %%s IN (%*) DO (set params=!params! %%s)

#ECHO OFF
SETLOCAL
SET swparams=log tidy test convert truncate
FOR %%i IN (%swparams% other) DO SET "%%i="
FOR %%i IN (%*) DO (
SET "used="
FOR %%p IN (%swparams%) DO (IF /i "/%%p"=="%%~i" SET %%p=Y&SET used=Y)
IF NOT DEFINED used CALL SET other=%%other%% "%%~i"
)
ECHO =============paramsreport===========
FOR %%i IN (%swparams%) DO IF DEFINED %%i (ECHO %%i:set) ELSE (ECHO %%i:clear)
ECHO other=%other%
FOR %%i IN (%other%) DO ECHO %%i or %%~i
GOTO :EOF
Here's a way that should be extensible for you.
Simply set you switch-parameters into the list in swparams.
the parameter-names and OTHER are set to [nothing] to ensure they're not already set in the environment.
Ech supplied parameter is applied to %%i in turn, and matched against each defined swparam in turn. the variable USED is cleared before the match and if the match (of /switchparametername is found, the switch parameter is set and the USED flag is set.
if the used flag is not set gainst any of the switch parameters, then a parsing trick is used to accumulate any unrecognised strings into OTHER
The "%%~i" mechanism first dequotes the item in %%i, then quotes it. In this way, it ends up quoted, regardless of whether it originally has quotes or not.
The /i on the if performs a case-insensitive match.
hence running this batch
thisbatch /tidy "C:\some filename with spaces.txt"
will yield TIDY set to Y, LOG,test, convert, truncate not set and other set to "C:\some filename with spaces.txt"

#echo off
setlocal EnableDelayedExpansion
rem Get the single filepath element (with colon in second character):
set params=/tidy /log /truncate /convert D:\libdir
set filepath=
for %%a in (%params%) do (
set par=%%a
if "!par:~1,1!" == ":" (
set filepath=%%a
)
)
if defined filepath (
echo Filepath = %filepath%
) else (
echo Filepath not given
)
echo/
rem Get multiple filepath elements in an *array*:
set params=/log /tidy D:\cyclea\libfolder /test /convert D:\libdir /truncate
set i=0
for %%a in (%params%) do (
set par=%%a
if "!par:~1,1!" == ":" (
set /A i+=1
set filepath[!i!]=%%a
)
)
echo There are %i% filepath elements:
for /L %%i in (1,1,%i%) do (
echo %%i- !filepath[%%i]!
)
You may review a further description on array management at this post: Arrays, linked lists and other data structures in cmd.exe (batch) script

Related

Eliminate strings with wildcard in batch file variable

Just noticed that VAR=%VAR:*STRING% does eliminate the previous string but VAR=%VAR:STRING*% doesn't elimiate next string
so how to eliminate the next string ? my current code is :
:CheckEnvironmentVariable Location Variable Value
IF [%1] EQU [System] (
ECHO Querying system
) ELSE (
IF [%1] EQU [User] (
ECHO Querying User Environments
FOR /F "usebackq tokens=*" %%x IN (`REG QUERY "HKCU\Environment"`) DO (
SET CURRVARS=%%x&&SET CURRVARS=!CURRVARS:REG_*=!
ECHO !CURRVARS!
)
) ELSE (
ECHO ERROR ^^! Invalid Environment Variable Location "%1"
)
)
EXIT /B
which is doen't work as expected
I am afraid I don't understand what you want to do. However, I guess that is related to this "possible" solution:
#echo off
setlocal EnableDelayedExpansion
set "VAR=This is a STRING long value"
echo VAR: %VAR%
rem Eliminate string "previous" to "STRING" (including it)
set "tail=%VAR:*STRING=%"
echo Tail: "%tail%"
rem Eliminate string "next" to "STRING" (not including it)
set "head=!VAR:%tail%=!"
echo Head1: "%head%"
rem Eliminate string "next" to "STRING" (including it)
set "head=!VAR:STRING%tail%=!"
echo Head2: "%head%"
Output:
VAR: This is a STRING long value
Tail: " long value"
Head1: "This is a STRING"
Head2: "This is a "
Based upon the code you have submitted, I'm not even sure why you would want to try to split the string at that particular place. There is a consistent string in every single line that would be returned by your reg.exe command, and that is REG_. The beauty of that paricular string is that it will always be non space separated, non localized, and never contain special characters. If you split at that point, you know that the substring you're looking for, will always be every token following its remainder, e.g. EXPAND_SZ your string(s); SZ your string(s).
So here's some example code which uses that method, but please be aware that it wil not work as intended should you have any variable defined within the System or User Environments with names including the case insensitive string REG_:
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
:Ask4Var
ClS
Set "sName="
Set /P "sName=Please enter the name of the variable you wish to verify>"
If Not Defined sName GoTo Ask4Var
(
Set %sName%
) 2>NUL | %SystemRoot%\System32\findstr.exe /BIL "%sName%=" 1>NUL || (
Echo There is no variable named %sName% in the current environment
%SystemRoot%\System32\timeout.exe /T 3 1>NUL
GoTo Ask4Var
)
Set "Env=System"
Set "RootKey=HKLM"
Set "SubKey=System\CurrentControlSet\Control\Session Manager\Environment"
%SystemRoot%\System32\choice.exe /C SU /N /M "[S]ystem OR [U]ser?"
If ErrorLevel 2 (
Set "Env=User"
Set "RootKey=HKCU"
Set "SubKey=Environment"
)
Set "Reg=%SystemRoot%\System32\reg.exe"
Set "ValueString="
For /F Delims^=^ EOL^= %%G In (
'%Reg% Query "%RootKey%\%SubKey%" /V /F "%sName%" /E ^|
%SystemRoot%\System32\find.exe "REG_"'
) Do (
Set "Result=%%G"
SetLocal EnableDelayedExpansion
For /F "Tokens=1,*" %%H In ("!Result:*REG_=!") Do (
EndLocal
Set "ValueString=%%I"
)
)
If Not Defined ValueString (
Echo There is no variable named %sName% in the %Env% environment
%SystemRoot%\System32\timeout.exe /T 3 1>NUL
GoTo Ask4Var
)
Echo The expanded string value of %sName% is %ValueString%.
%SystemRoot%\System32\timeout.exe /T 7 1>NUL
GoTo :EOF

Loop recursively into subfolders and look for a substring into files

I would like to create a script that loops reccursively through subfolders of D:\MyFolder\ for example, to find multiple files named MyFile.txt
then look into each file for the keyword FROM and retrieve the string between the FROM and the next semicolon ;.
Sample of MyFile.txt:
LOAD
Thing1,
Thing2,
Thing3,
FROM
Somewhere ;
The desired result is: Somewhere.
(The position of the semicolon ; can be in another line).
I did some tries but I did not succeed in writing a correct script:
#echo off
SET PATH="D:\MyFolder\"
FOR /R %PATH% %%f IN (MyFile.txt) DO (
FOR /F "delims=FROM eol=;" %%A in (%%f) do (
set str=%%A
ECHO %str%
)
)
If it can't be done in batch, please let me know in which language I can do it easily. I would like to have an executable script in the end.
There are some issues in your code:
The delims option of for /F defines characters but not words to be used as delimiter for parsing text files. To find a word, use findstr instead (you could use its /N option to derive the position/line number of the search string).
The eol option of for /F defines a character to ignore a line in case it occurs at the beginning (or it is preceded by delimiters only).
for /R does actually not search for files in case there are no wild-cards (?, *) in the set (that is the part in between parentheses). The dir /S command does, so you can work around this by wrapping a for /F loop around dir /S.
The PATH variable is used by the system to find executables, like findstr, so you must not overwrite it; use a different variable name instead.
Here is the way I would probably do it (supposing any text following the keyword FROM needs to be returned also):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=D:\MyFolder" & rem // (root directory of the tree to find files)
set "_FILE=MyFile.txt" & rem // (name of the files to find in the tree)
set "_WORD=FROM" & rem // (keyword to be searched within the files)
set "_CHAR=;" & rem // (character to be searched within the files)
rem // Walk through the directory tree and find matching files:
for /F "delims=" %%F in ('dir /B /S "%_ROOT%\%_FILE%"') do (
rem // Retrieve the line number of each occurrence of the keyword:
for /F "delims=:" %%N in ('findstr /N /I /R "\<%_WORD%\>" "%%~F"') do (
rem // Process each occurrence of the keyword in a sub-routine:
call :PROCESS "%%~F" %%N
)
)
endlocal
exit /B
:PROCESS
rem // Ensure the line number to be numeric and build `skip` option string:
set /A "SKIP=%~2-1"
if %SKIP% GTR 0 (set "SKIP=skip^=%SKIP%") else set "SKIP="
rem // Read file starting from line containing the found keyword:
set "FRST=#"
for /F usebackq^ %SKIP%^ delims^=^ eol^= %%L in ("%~1") do (
set "LINE=%%L"
setlocal EnableDelayedExpansion
rem // Split off everything up to the keyword from the first iterated line:
if defined FRST set "LINE=!LINE:*%_WORD%=!"
rem /* Split read line at the first occurrence of the split character;
rem the line string is augmented by preceding and appending a space,
rem so it is possible to detect whether a split char. is there: */
for /F "tokens=1,* delims=%_CHAR% eol=%_CHAR%" %%S in (" !LINE! ") do (
endlocal
set "TEXT=%%S"
set "RMND=%%T"
set "ITEM=%~1"
setlocal EnableDelayedExpansion
rem // Check whether a split character is included in the line string:
if not defined RMND (
rem // No split char. found, so get string without surrounding spaces:
set "TEXT=!TEXT:~1,-1!"
) else (
rem // Split char. found, so get string without leading space:
set "TEXT=!TEXT:~1!"
)
rem // Trimm leading white-spaces:
for /F "tokens=*" %%E in ("!TEXT!") do (
endlocal
set "TEXT=%%E"
setlocal EnableDelayedExpansion
)
rem // Return string in case it is not empty:
if defined TEXT echo(!ITEM!;!TEXT!
rem // Leave sub-routine in case split char. has been found:
if defined RMND exit /B
)
endlocal
set "FRST="
)
exit /B

batch replace spaces between quotes in file and then remove all quotes

I want create a batch to replace spaces with a + sign if the space is in between quotes. Then I want to remove the quotes from a text file. How can I accomplish this?
So I want to change a line like this:
2016-01-11 14:45:09 Server 127.0.0.1 GET /global/images/logo_small.jpg - 80 - 173.252.120.117 "facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)" "-" www.vietnam.ttu.edu 200 200 200 1868 0
To this line.
2016-01-11 14:45:09 Server 127.0.0.1 GET /global/images/logo_small.jpg - 80 - 173.252.120.117 facebookexternalhit/1.1+(+http://www.facebook.com/externalhit_uatext.php) - www.vietnam.ttu.edu 200 200 200 1868 0
Thanks
You could use JREPL.BAT to arrive at a very concise and efficient solution. JREPL is a pure script based (JScript/batch) regular expression text processing utility that runs on any version of Windows from XP onward.
jrepl "\q| " "q=!q;''|q?'+':' '" /j /x /t "|" /jbegln "q=false" /f test.txt /o -
For this solution I use the /T option, which is very similar to the unix tr utility, or the sed y command.
I define two search terms, the first for a quote (The \X option enables the \q escape sequence), and the second for a space.
The /J option treats replacement strings as JScript. The first replacement string for the quote toggles a "q" variable TRUE or FALSE, and replaces the quote with an empty string. The second replacement string conditionally replaces the space with a plus or space, depending on the state of the "q" variable.
The /JBEGLN option initializes the "q" variable to FALSE at the beginning of each line.
The /F option specifies the input file, and the /O - option specifies that the output overwrites the original file.
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q34732271.txt"
FOR /f "usebackqdelims=" %%a IN ("%filename1%") DO ECHO %%a&SET "line="&CALL :process %%a
GOTO :EOF
:process
SET "addition=%~1"
IF not DEFINED addition ECHO %line:~1%&GOTO :eof
IF "%~1"==%1 (
REM quoted
SET "line=%line% %addition: =+%"
) ELSE (
SET "line=%line% %addition%"
)
shift
GOTO process
You would need to change the setting of sourcedir to suit your circumstances.
I used a file named q34732271.txt containing your data for my testing.
The echo %%a shows your one line of data on the screen and the echo within the :process routine shows that line processed.
Batch is not an ideal language to process strings as it exhibits sensitivity to many symbols. This process should work provided you are happy to have space-strings compressed and the source string does not contain , ;,tab % or any other symbol that cmd treats specially.
Here is a pure batch-file solution that walks through the characters in each line in file line.txt, replaces all SPACEs in between a pair of quotation marks "" by + signs and stores the result in text_new.txt. The input string may contain any characters, even special ones:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Define global constants here:
set "INFILE=line.txt"
set "OUTFILE=line_new.txt"
set "SEARCH= "
set "REPLACE=+"
set "KEEPQUOTES="
set "QUOTE="""
set "QUOTE=%QUOTE:~,1%"
set "QFLAG="
> "%OUTFILE%" (
for /F usebackq^ delims^=^ eol^= %%L in ("%INFILE%") do (
set "LINE=%%L"
call :SUB LINE
)
)
endlocal
exit /B
:SUB
setlocal EnableDelayedExpansion
set "LINE=!%1!"
set "LINENEW="
set /A "POS=0"
:LOOP
set "CHAR=!LINE:~%POS%,1!"
set /A "POS+=1"
if not defined CHAR (
echo(!LINENEW!
endlocal
exit /B
)
if "!CHAR!"=="!QUOTE!" (
if defined QFLAG (
set "QFLAG="
) else (
set "QFLAG=Quoted"
)
if defined KEEPQUOTES (
set "LINENEW=!LINENEW!!CHAR!"
)
) else if defined QFLAG (
if "!CHAR!"=="!SEARCH!" (
set "LINENEW=!LINENEW!!REPLACE!"
) else (
set "LINENEW=!LINENEW!!CHAR!"
)
) else (
set "LINENEW=!LINENEW!!CHAR!"
)
goto :LOOP

Escape asterisk in Windows Batch File's FOR Loop

When running the following code in a windows batch file everything works aside from the string containing the asterisk, which is skipped. Checking the passed parameters by number (i.e. echo(%~6) I can see the asterisk - it's only when passed to the FOR loop that I have an issue:
#echo off
setlocal enableextensions enabledelayedexpansion
call:Concat cmd "this is a demo" " of concat functionality." " Hopefully it will work;" " but it doesn't when I pass an" " * asterisk" " character"
echo !cmd!
#goto:end
#goto:eof
:Concat
::Concatenates a given list of strings without including their quotes
::1 - output variable
::2* - strings to concat
echo(%*
set /a xx=0
set Concat_tempFlag=0
set Concat_temp=
for %%A in (%*) do (
set /a xx=!xx!+1
echo !xx! - %%A
if !Concat_tempFlag!==1 (
set Concat_temp=!Concat_temp!%%~A
) else (
set Concat_tempFlag=1
)
)
set "%~1="%Concat_temp%""
#goto:eof
:End
echo(Bye
exit /b 0
I've attempted for /F (tokens=*) %%A in ('echo(%*') do ( as suggested here: Batch FOR loop with asterisk (and variations thereof) but with no luck. Any ideas? Thanks in advance.
Found the solution here: I need to match or replace an asterisk * in a batch environmental variable using only native Windows commands. Is this possible?
Full code below:
#echo off
setlocal enableextensions enabledelayedexpansion
set DEFAULT_AsteriskMarker=_xAsteriskMarkerx_
call:Concat cmd "this is a demo" " of concat functionality." " Hopefully it will work;" " but it doesn't when I pass an" " * asterisk" " character"
echo !cmd!
#goto:end
#goto:eof
:Concat
::Concatenates a given list of strings without including their quotes
::1 - output variable
::2* - strings to concat
set Concat_StringsToConcat=%*
echo(%Concat_StringsToConcat%
call:AsteriskFix Concat_StringsToConcat
set /a xx=0
set Concat_tempFlag=0
set Concat_temp=
for %%A in (%Concat_StringsToConcat%) do (
set /a xx=!xx!+1
echo !xx! - %%A
if !Concat_tempFlag!==1 (
set Concat_temp=!Concat_temp!%%~A
) else (
set Concat_tempFlag=1
)
)
set "%~1="!Concat_temp:%DEFAULT_AsteriskMarker%=*!"
#goto:eof
:AsteriskFix
::https://stackoverflow.com/questions/11685375/i-need-to-match-or-replace-an-asterisk-in-a-batch-environmental-variable-using
set AsteriskFix_temp=!%~1!
if "%~2"=="" (
set AsteriskFix_marker=%DEFAULT_AsteriskMarker%
) else (
set AsteriskFix_marker=%~2
)
call:StrLen AsteriskFix_temp AsteriskFix_len
for /l %%x in (0,1,%AsteriskFix_len%) do if not "!AsteriskFix_temp:~%%x,1!"=="" if "!AsteriskFix_temp:~%%x,1!"=="*" (
set /a AsteriskFix_plusone=%%x+1
for /l %%y in (!AsteriskFix_plusone!, 1, !AsteriskFix_plusone!) do (
set AsteriskFix_temp=!AsteriskFix_temp:~0,%%x!%AsteriskFix_marker%!AsteriskFix_temp:~%%y!
)
)
set "%~1=!AsteriskFix_temp!"
#goto:eof
:StrLen
::http://www.dostips.com/DtCodeCmdLib.php#strLen
set "StrLen_str=A!%~1!" &:: keep the A up front to ensure we get the length and not the upper bound
::it also avoids trouble in case of empty string
set "StrLen_len=0"
for /L %%A in (12,-1,0) do (
set /a "StrLen_len|=1<<%%A"
for %%B in (!StrLen_len!) do if "!StrLen_str:~%%B,1!"=="" set /a "StrLen_len&=~1<<%%A"
)
IF "%~2" NEQ "" SET /a %~2=%StrLen_len%
#goto:eof
:End
echo(Bye
exit /b 0
Thanks to James K
The link you provided leads to the right answer:
There is no way to preserve an asterisk (nor a question mark) in the set of a normal (no /F option) FOR command (they are always changed to file names); you need to separate the parameters in a FOR /F command. If you also want to process each parameter in a FOR loop, then the second FOR can NOT be in the same context, so you must CALL a subroutine to change the context

LastIndexOf in Windows batch

I need to implement a function in a Windows batch script to get the LastIndexOf a character into a given string.
For example: Given the following string, I need to get the last index of character '/':
/name1/name2/name3
^
So I need to get the value:
12
Joey's solution works, but the character to find is hard coded, and it is relatively slow.
Here is a parametized function that is fast and can find any character (except nul) within the string. I pass the name of variables containing the string and the character instead of string literals so that the function easily supports all characters.
#echo off
setlocal
set "test=/name1/name2/name3"
set "char=/"
::1st test simply prints the result
call :lastIndexOf test char
::2nd test stores the result in a variable
call :lastIndexOf test char rtn
echo rtn=%rtn%
exit /b
:lastIndexOf strVar charVar [rtnVar]
setlocal enableDelayedExpansion
:: Get the string values
set "lastIndexOf.char=!%~2!"
set "str=!%~1!"
set "chr=!lastIndexOf.char:~0,1!"
:: Determine the length of str - adapted from function found at:
:: http://www.dostips.com/DtCodeCmdLib.php#Function.strLen
set "str2=.!str!"
set "len=0"
for /L %%A in (12,-1,0) do (
set /a "len|=1<<%%A"
for %%B in (!len!) do if "!str2:~%%B,1!"=="" set /a "len&=~1<<%%A"
)
:: Find the last occurrance of chr in str
for /l %%N in (%len% -1 0) do if "!str:~%%N,1!" equ "!chr!" (
set rtn=%%N
goto :break
)
set rtn=-1
:break - Return the result if 3rd arg specified, else print the result
( endlocal
if "%~3" neq "" (set %~3=%rtn%) else echo %rtn%
)
exit /b
It wouldn't take much modification to create a more generic :indexOf function that takes an additional argument specifying which occurance to find. A negative number could specify to search in reverse. So 1 could be the 1st, 2 the 2nd, -1 the last, -2 penultimate, etc.
(Note: I'm assuming Windows batch files because, frankly, I have only seen a single question asking for an actual DOS batch file here so far. Most people simply misattribute “DOS” to anything that has a window of gray-on-black monospaced text without knowing what they're actually talking of.)
Just loop through it, updating the index as you go:
#echo off
setlocal enabledelayedexpansion
set S=/name1/name2/name3
set I=0
set L=-1
:l
if "!S:~%I%,1!"=="" goto ld
if "!S:~%I%,1!"=="/" set L=%I%
set /a I+=1
goto l
:ld
echo %L%
I know this question is a bit old now, but I needed a function that could find the location of a substring (of any length) within a string, and adapted dbenham's solution for my purposes. This function also works with individual characters within a string, as asked for in the original question, and can search for specific instances (as suggested by dbenham).
To use this function, the actual strings must be passed. Dbenham does note that this supports fewer characters than passing the actual variables, but I find that this variant is more reuseable (especially with pipes).
The third argument takes the instance that should be found, with negative numbers specifying to search from the end. The index returned is the offset from the start of the string to the first character in the substring.
#ECHO off
SET search_string=sub
CALL :strIndex "The testing subjects subjects to testing." "%search_string%" -2
ECHO %ERRORLEVEL%
PAUSE
EXIT
:strIndex string substring [instance]
REM Using adaptation of strLen function found at http://www.dostips.com/DtCodeCmdLib.php#Function.strLen
SETLOCAL ENABLEDELAYEDEXPANSION
SETLOCAL ENABLEEXTENSIONS
IF "%~2" EQU "" SET Index=-1 & GOTO strIndex_end
IF "%~3" EQU "" (SET Instance=1) ELSE (SET Instance=%~3)
SET Index=-1
SET String=%~1
SET "str=A%~1"
SET "String_Length=0"
FOR /L %%A IN (12,-1,0) DO (
SET /a "String_Length|=1<<%%A"
FOR %%B IN (!String_Length!) DO IF "!str:~%%B,1!"=="" SET /a "String_Length&=~1<<%%A"
)
SET "sub=A%~2"
SET "Substring_Length=0"
FOR /L %%A IN (12,-1,0) DO (
SET /a "Substring_Length|=1<<%%A"
FOR %%B IN (!Substring_Length!) DO IF "!sub:~%%B,1!"=="" SET /a "Substring_Length&=~1<<%%A"
)
IF %Substring_Length% GTR %String_Length% GOTO strIndex_end
SET /A Searches=%String_Length%-%Substring_Length%
IF %Instance% GTR 0 (
FOR /L %%n IN (0,1,%Searches%) DO (
CALL SET StringSegment=%%String:~%%n,!Substring_Length!%%
IF "%~2" EQU "!StringSegment!" SET /A Instance-=1
IF !Instance! EQU 0 SET Index=%%n & GOTO strIndex_end
)) ELSE (
FOR /L %%n IN (%Searches%,-1,0) DO (
CALL SET StringSegment=%%String:~%%n,!Substring_Length!%%
IF "%~2" EQU "!StringSegment!" SET /A Instance+=1
IF !Instance! EQU 0 SET Index=%%n & GOTO strIndex_end
))
:strIndex_end
EXIT /B %Index%

Resources