I have written a script that contains a function that should loop through a list and return a value given the index of the item in said list. I have a function called: :find that should take 2 arguments: the list and the item position. I am unsure of how to process the multiple parameters in the function. This script runs fine if I replace %LIST% with %MY_LIST% inside the loop and I remove the %MY_LIST% from the argument list tht is passed to call :find, but I really want to know how to pass multiple arguments. I take it that they are just passed to the function as a whole string...
#echo off
setlocal enableDelayedExpansion
cls
:: ----------------------------------------------------------
:: Variable declarations
:: ----------------------------------------------------------
set RETURN=-1
set MY_LIST=("foo" "bar" "baz")
set TARGET_INDEX=1
:: ----------------------------------------------------------
:: Main procedure
:: ----------------------------------------------------------
call :log "Finding item %TARGET_INDEX%..."
call :find %MY_LIST% %TARGET_INDEX%
call :log "The value is: %RETURN%"
goto Exit
:: ----------------------------------------------------------
:: Function declarations
:: ----------------------------------------------------------
:find
call :log "Called `:find` with params: [%*]"
set /a i=0
set LIST=%~1 & shift
for %%a in %LIST% do (
if !i! == %~1 (
set RETURN=%%a
)
set /a i=!i!+1
)
goto:EOF
:printDate
for /f "tokens=2-4 delims=/ " %%a in ('echo %DATE%') do (
set mydate=%%c/%%a/%%b)
for /f "tokens=1-3 delims=/:./ " %%a in ('echo %TIME%') do (
set mytime=%%a:%%b:%%c)
echo|set /p="[%mydate% %mytime%] "
goto:EOF
:log
call :printDate
echo %~1
goto:EOF
:: ----------------------------------------------------------
:: End of script
:: ----------------------------------------------------------
:Exit
Update
My script now works fine; thanks to nephi12. http://pastebin.com/xGdFWmnM
call :find "%MY_LIST%" %TARGET_INDEX%
goto :EOF
:find
echo %~1 %~2
goto :EOF
they are passed the same as args to the script... ;)
Here is another method to do an index lookup on a space delimited list of values. Define the list without enclosing parentheses. Single words don't need to be quoted. Phrases with space, tab, semicolon, or equal must be quoted. Also values with special characters like & and | should be quoted.
Then reverse the order of your :FIND arguments - first the index, followed by the actual list. Use SHIFT in a FOR /L loop to get the desired index value into the first argument.
One advantage of this solution is that it can support any number of values, as long as they fit within the 8191 character per line limit. The nephi12 solution is limited to 9 values.
#echo off
setlocal
set MY_LIST=foo bar baz "Hello world!"
call :find %~1 %MY_LIST%
echo return=%return%
exit /b
:find index list...
for /L %%N in (1 1 %~1) do shift /1
set "return=%~1"
exit /b
Try this, I think it answers your question. Put it in a bat file and then build whatever else you need around it after you see this work. Execute it with a quoted string from a command prompt. YourBatFile "Arg1 Arg2 Arg3 Etc"
#echo off
call :DoSomethingWithEach %~1
goto :eof
:DoSomethingWithEach
for %%a in (%*) do echo.%%a
goto :eof
Related
I faced with problem using windows cmd. I need found and replace last found substring. For instance I have a string - #192.168.0.1:1521:SID. I need to replace the last colon with a slash and recieve #192.168.0.1:1521/SID.
How can I do this?
It looks like you have a fairly well defined format: IPAddress:Number:SID, so this could be treated as replacing the 2nd : with a /
#echo off
set val=#192.168.0.1:1521:SID
for /f "tokens=1,2,* delims=:" %%a in ("%val%") do set newval=%%a:%%b/%%c
echo %newval%
yourcommand %newval%
You can optimize if the format is always the same (e.g. always the 4th char from the end) but as a general solution I would code it like below.
set result=
set left=
set right=#192.168.0.1:1521:SID
:loop
call :sub1 "%right%"
if %result% equ 1 goto :loop
#echo %left%/%right%
goto :eof
:sub1
for /f "delims=: tokens=1*" %%i in ("%~1") do (
if ["%%j"]==[""] (
set /a result=0
goto :eof
)
if ["%left%"]==[""] (
set left=%%i
) else (
set left=%left%:%%i
)
set /a result=1
set right=%%j
)
goto :eof
Explanation:
The code in sub1 splits the argument on the first colon from the left unless there is no colon in the argument - in this case it sets the result to 0 and returns.
The left part is added to the left-variabel, the right part is set to the right-variable.
The main loop calls the sub sub1 until there is no more split and you're done.
#echo off
:start
set string=
set lo=1
set a=0
set b=0
set cl=1
set cloop=
set google=0
set k=0
set r=0
set id=
set t=0
set f=0
set /p string=?
if defined string (
echo %string%
goto loop
) else (
echo please enter a string
goto start
)
:loop
set a=
for /f "tokens=%lo%" %%G IN ("%string%") DO echo %%G
if defined a (
echo %a%
set google=0
set /p cloop=<greetings.txt
pause
:cloop
set b=
for /f "tokens=%cl%" %%g IN ("%cloop%") DO set b=%%g
if defined string (
if %a%==%b% goto greetings
set /a cl=%cl%+1
goto cloop
) else (
set cl=0
set /a lo=%lo%+1
goto loop
)
) else (
goto google
)
:greetings
set f=0
set k=0
set r=0
set /p id=<greetingtone.dat
for /f "tokens=%cl%" %%g IN ("%id%") DO set t=%%g
start greeting.bat
call greeting.bat
goto talk
:google
echo not done yet
pause
goto start
i have narrowed it down to this line
if %a%==%b% goto greetings
when i remove it it runs
i have looked but i have no idea why it does not work
please help the greetings.txt has "hi hello grunt"
i think it might be the variables
If %a% or %b% are empty values, it is likely the compare is incomplete, and it is saying that the goto is not expected yet. For instance, if you type the following at a C:\ prompt:
c:\>if a== echo ok
c:\>if ==a echo ok
echo was unexpected at this time.
c:\>if == echo ok
ok was unexpected at this time.
c:\>
If you enclose each value in quotes, then the comparison will still work even if one or both of the values are empty. For instance:
if "%a%"=="%b%" goto greetings
The normal reason for that an unexpected word in an IF statement is that IF has a very specific syntax, IF item1 operator item2 actionstatement(s).
What is likely to be happening is that item1 AND item2 appear to be missing, so IF resolves that as IF == goto greetings. Since goto is not one of its known operators (==, equ, neq, leq, lss, geq, gtr`) then it complains.
The question from here is - why do %a% and %b% appear to be empty?
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed.
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered. In your case, that means the outermost IF - in if defined string.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
Next problem is using a label within a block. Not a good idea. On some versions, a label will terminate the block. Call a subroutine instead.
call :cloop
...
goto start
:cloop
(whatever needs to be done)
goto :eof
(note that :cloop and :EOF have a required colon. on cloop it means "this is an internal subroutine - it's in the cuurrent batchfile." :EOF is a predefined label understood by CMD to mean end of file.)
%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
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%
I need to get last argument passed to windows batch script, how can I do that?
This will get the count of arguments:
set count=0
for %%a in (%*) do set /a count+=1
To get the actual last argument, you can do
for %%a in (%*) do set last=%%a
Note that this will fail if the command line has unbalanced quotes - the command line is re-parsed by for rather than directly using the parsing used for %1 etc.
The easiest and perhaps most reliable way would be to just use cmd's own parsing for arguments and shift then until no more are there.
Since this destroys the use of %1, etc. you can do it in a subroutine:
#echo off
call :lastarg %*
echo Last argument: %LAST_ARG%
goto :eof
:lastarg
set "LAST_ARG=%~1"
shift
if not "%~1"=="" goto lastarg
goto :eof
An enhanced version of joey's answer:
#ECHO OFF
SETLOCAL
:test
:: https://stackoverflow.com/a/5807218/7485823
CALL :lastarg xxx %*
ECHO Last argument: [%XXX%]
CALL :skiplastarg yyy %*
ECHO skip Last argument: [%yyy%]
GOTO :EOF
:: Return all but last arg in variable given in %1
:skiplastarg returnvar args ...
SETLOCAL
SET $return=%1
SET SKIP_LAST_ARG=
SHIFT
:skiplastarg_2
IF NOT "%~2"=="" SET "SKIP_LAST_ARG=%SKIP_LAST_ARG% %1"
SHIFT
IF NOT "%~1"=="" GOTO skiplastarg_2
ENDLOCAL&CALL SET "%$return%=%SKIP_LAST_ARG:~1%"
GOTO :EOF
:: Return last arg in variable given in %1
:lastarg returnvar args ...
SETLOCAL
SET $return=%1
SET LAST_ARG=
SHIFT
:LASTARG_2
SET "LAST_ARG=%1"
SHIFT
IF NOT "%~1"=="" GOTO lastarg_2
ENDLOCAL&call SET %$return%=%LAST_ARG%
GOTO :EOF
Run it with the arguments:
abe "ba na na" "cir cle"
and get:
Last argument: ["cir cle"]
skip Last argument: [abe "ba na na"]
set first=""
set last=""
for %%a in (%*) do (
SETLOCAL ENABLEDELAYEDEXPANSION
if !first!=="" (set first=!last!) else (set first=!first! !last!)
set last=%%a
)
ENDLOCAL & set "last=%last%" & set "first=%first%"
echo %last% "and" %first%