8

I'm trying to figure out if a string starts with a substring :

rem GetInput
if "%result%"=="cd *" goto Cd
:Cd
if "%result%"=="cd meow" Echo Going to directory 'meow'...

Unfortunately this not looping back to :Cd when it should. How can I detect if th string starts with cd ?

Gaby
  • 97
  • 1
  • 2
  • 11
  • What do you mean by "the script crash"? What results are you actually getting? If it gives you an error, please edit your question to include it. – Wes Larson Mar 25 '16 at 23:02
  • That was not supposed to be here, fixed the question. – Gaby Mar 25 '16 at 23:42

3 Answers3

24

If I understand this correctly, you're trying to determine whether that value for %result% begins with 'cd', and you're unsuccessfully using a * as a wildcard to determine this.

There are a few different ways to handle this. Here's one way: You can check to see if the first two characters are equal to 'cd':

if /i "%result:~0,2%"=="cd" goto Cd

set /? has more on this.

Wes Larson
  • 1,042
  • 8
  • 17
2

here's a comparatively robust and universal startsWith subroutine (with two examples).It sets errorlevel to 1 if the strings starts with an another string and to 0 if does not.

@echo off
set string1=abc123123
set checker1=abc

call :startsWith %string1% %checker1%
if %errorlevel% equ 1 (
    echo %string1% starts with %checker1%
) else (
    echo %string1% does NOT start with %checker1%
)

set string2=xyz123123 abc

call :startsWith %string2% %checker2%
if %errorlevel% equ 1 (
    echo %string2% starts with %checker2%
) else (
    echo %string2% does NOT start with %checker2%
)




exit /b 0

::::::::::::::::::::::::::::::::::::::::
:::----- subroutine starts here ----::::
:startsWith [%1 - string to be checked;%2 - string for checking ] 
@echo off
rem :: sets errorlevel to 1 if %1 starts with %2 else sets errorlevel to 0

setlocal EnableDelayedExpansion

set "string=%~1"
set "checker=%~2"
rem set "var=!string:%~2=&echo.!"
set LF=^


rem ** Two empty lines are required
rem echo off
for %%L in ("!LF!") DO (
    for /f "delims=" %%R in ("!checker!") do ( 
        rem set "var=!string:%%~R%%~R=%%~L!"
        set "var=!string:%%~R=#%%L!"
    )
)
for /f "delims=" %%P in (""!var!"") DO (
    if "%%~P" EQU "#" (
        endlocal & exit /b 1
    ) else (
        endlocal & exit /b 0
    )
)
::::::::::::::::::::::::::::::::::::::::::::::::::
npocmaka
  • 55,367
  • 18
  • 148
  • 187
2

The title is broader than the use case, which makes a generic startsWith a bit of overkill for the specific question -- as Wes says, for this specific question, you could just check the first two characters and check for cd.

But that in mind, let's answer the more generic question from the title.

A Generic startsWith

DOSTips.com's solution for startsWith looks pretty good.

:StartsWith text string -- Tests if a text starts with a given string
::                      -- [IN] text   - text to be searched
::                      -- [IN] string - string to be tested for
:$created 20080320 :$changed 20080320 :$categories StringOperation,Condition
:$source https://www.dostips.com
SETLOCAL
set "txt=%~1"
set "str=%~2"
if defined str call set "s=%str%%%txt:*%str%=%%"
if /i "%txt%" NEQ "%s%" set=2>NUL
EXIT /b

An easier to read refactor? (TL;DR -- this is the answer)

That seems to work, but it doesn't really return a value and its variable names are very 1990s.

I hacked to this:

:startsWith
set "haystack1=%~1"
set "needle2=%~2"

if defined needle2 call set "s=%needle2%%%haystack1:*%needle2%=%%"
if /i "%haystack1%" NEQ "%s%" (
    exit /b 0
)
EXIT /b 1

That will return a 1 in %errorlevel% for yes, does-start-with, and 0 for no, does-NOT-start-with. You could argue that does-start-with should return a 0 for "no error". If you feel strongly, you're obviously welcome to swap those in your implementation. I'm going with the "zero is traditionally falsy so it represents a false return value". YMMV, .

Test the implementation

And here's a full example to test...

@ECHO OFF

call :startsWith slappy slap
echo %errorlevel%
echo.

call :startsWith slap slappy
echo %errorlevel%
echo.

call :startsWith slappy spam
echo %errorlevel%
echo.

call :startsWith slappy lap
echo %errorlevel%
echo.

call :startsWith spam
echo %errorlevel%
echo.
exit /b 0

:startsWith
set "haystack1=%~1"
set "needle2=%~2"
set "s="

REM I think this is okay b/c if %2 was defined, %1 kinda has to be too.
if defined needle2 call set "s=%needle2%%%haystack1:*%needle2%=%%"

echo needle:   %needle2%
echo haystack: %haystack1%
echo s:        %s%

if /i "%haystack1%" NEQ "%s%" (
    exit /b 0
)
EXIT /b 1

Output

needle:   slap
haystack: slappy
s:        slappy
1

needle:   slappy
haystack: slap
s:        slappyslap
0

needle:   spam
haystack: slappy
s:        spamslappy
0

needle:   lap
haystack: slappy
s:        lappy
0

needle:
haystack: spam
s:
0

It's a neat trick. It basically...

  1. Makes a new string that starts with needle
  2. If needle occurs anywhere in haystack, add the portion of haystick that occurs after needle's appearance to the new string. Otherwise add all of haystack to your new string.
  3. If this new string matches haystack, you win! Else you lose.

So if needle is slap and haystack is slappy, %s% will be slap plus [everything after slap in slappy which is...] py. That gives us slappy, which matches what was in haystack, and we win.

If needle is spam and haystack is slappy, %s% will start with spam and then, since spam isn't in slappy, we add all of haystack. That leaves us with spamslappy and we lose. (spamslappy? Now that's comedy.)

And a neat fail is when you look for something that is in but not at the start of the haystack, like in our lap example. That is, if the needle is lap and haystack is slappy, %s% will start with lap and then, everything from the end of lap in slappy also gives py, we have lap plus py or lappy, which isn't slappy, and we lose again, but in a new, cool way.


Your batch syntax questions answered (maybe)

What's the tilde in set "needle2=%~2"?

Argument quote removal. A tilde sign before an command-line argument (such as "%~1") indicates to remove the surrounding quotes from the parameter. Such if the value for %1 is "Hi" then %~1 will expand to only Hi.

call set "s=%str%%%txt:*%str%=%%" is a little more convoluted.

First understand that the colon (:) means we're using "the string substitution feature":

This code taken from the same pagereplaces needs with has in %str:

set str=%str:needs=has% 
echo %str%

Then we need to know that...

"StrToFind" [after the :] can begin with an asterisk, in which case it will replace all characters to the left of "StrToFind".
...

::Delete the character string 'ab' and everything before it
SET _test=12345abcabc
SET _result=%_test:*ab=% 
ECHO %_result%          =cabc

Then see this answer:

The call there causes another layer of variable expansion, making it necessary to [escape] the original % signs but it all works out in the end.

Now call set "s=%str%%%txt:*%str%=%%" should make sense.

Another answer on that question shows that the method of startsWith from DOSTips is a little less finnicky than the other answer here as...

You can use !, but you must have the ENABLEDELAYEDEXPANSION switch set.

We're avoiding that.

And, finally...

If you are aghast at the insanity of all these arcane rules, you should be learning PowerShell, it's awesome!

Ha! Okay, but we can still get it done without leaving batch. Enjoy!

ruffin
  • 16,507
  • 9
  • 88
  • 138