2

Question: How to compare user entered string that contains special characters, for example 7^7%7&7=7"7!7 within Windows Batch?

Unfinished Code:

set/p str1=URL: 
rem if %str1%==7^7%7&7=7"7!7 (  // syntax error
rem if "%str1%"=="7^^7%%7^&7=7^"7!7" (  // still syntax error
    echo Nice
) else (
    echo Bad
)

Expect output:

> URL: 7^7%7&7=7"7!7
> Nice
> URL: 42
> Bad

p.s. user entered string is actually valid url in real situation so all valid url characters are possible input, quotes is included for more general situation similar to this problem.

Byzod
  • 466
  • 4
  • 18
  • the `=` sign is a command token used by the search replace employed by batch substring modification, and cannot be replaced using substring modification. consider checking out [dave benhams jrepl.bat](https://www.dostips.com/forum/viewtopic.php?t=6044) – T3RR0R Dec 19 '21 at 08:38
  • Why do you need the `=`-sign espaced? I'm asking because I suspect an [X/Y Problem](https://xyproblem.info). Anyway, you cannot use [sub-string substitution](https://ss64.com/nt/syntax-replace.html), because the `=`-sign separates search and replace strings; you could however use [sub-string expansion](https://ss64.com/nt/syntax-substring.html) and a [`goto`](https://ss64.com/nt/goto.html) loop to walk through each character of the string and reassemble the string with replaced characters… – aschipfl Dec 19 '21 at 16:19
  • @aschipfl You're right, I add too much misleading and useless info in my question, let me think it over and clean it – Byzod Dec 20 '21 at 09:27

1 Answers1

2

The main issue is the unbalanced quotes. The condition:

if "%str1%"=="7^7%%7&7=7"7!7^" (
    echo Nice
) else (
    echo Bad
)

should no longer return a syntax error on its own (given that delayed variable expansion is disabled), as long as the variable str1 does not contain (unbalanced) quotes on its own. As you can see, the %-symbol must be doubled, independent on quotation, but other special characters must only be escaped when they appear outside of quotes. The last quotation mark is escaped, so it is not recognised as such.

But this will still fail if you enter 7^7%7&7=7"7!7 into the prompt because of the quotation mark in str1, because (immediate) variable expansion happens before special characters are detected. The only way to prevent the condition from failing, independent on the user entry, you must use delayed variable expansion:

set /P str1="URL: "
rem // At first, enable delayed variable expansion:
setlocal EnableDelayedExpansion
rem /* Then apply it by enclosing variables with `!!` rather than `%%`;
rem    there are now even more complex escape sequences necessary though: */
if "!str1!"=="7^^7%%7&7=7"7^^!7^" (
    echo Nice
) else (
    echo Bad
)
endlocal

As you may recognise, the right expression of the comparison looks quite strange now, since delayed expansion may require some additional escaping, depending on whether the expression contains exclamation marks.

To prevent the need of such quite confusing and illegible escaped expressions, assign the comparison string to a variable first while delayed expansion is still disabled:

set /P str1="URL: "
rem /* Predefine comparison expression; you always have to double `%`-symbols,
rem    and you still need to escape some special characters that appear unquoted,
rem    but escaping sequences do not depend on whether or not there is a `!`: */
set "cmp1=7^7%%7&7=7"7!7^"
rem // At first, enable delayed variable expansion:
setlocal EnableDelayedExpansion
rem // Then apply it by enclosing variables with `!!` rather than `%%`:
if "!str1!"=="!cmp1!" (
    echo Nice
) else (
    echo Bad
)
endlocal
aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • It's working perfectly, thanks! I didn't know delayed expansion affects escaping too, now it feels more like human things. But I'm not sure I get what you mean, especially the quote part, when you assign `cmp1`, it's `7^7%%7&7=7"7!7^"` while I thought it's `7^7%%7&7=7^"7!7"`, why it's the last quote that should be escaped? How to assign `cmp1` if I want user to type `"test"`? – Byzod Dec 20 '21 at 15:45
  • 1
    @Byzod The last escape of the quote isn't necessary. Your second question: It's `set "cmp="test""` – jeb Dec 20 '21 at 16:09
  • You need to be aware that `cmd.exe` reads the line from left to right and toggles quotation mode (initially off) every time it encounters an unescaped quotation mark; when quotation mode is on, escaping does no longer work since the escape character `^` is treated literally, which makes escaping anyway unnecessary; when quotation mode is off, you need to escape special characters. So when quotation mode is on, you therefore cannot escape quotation marks… – aschipfl Dec 20 '21 at 19:01
  • In case of unbalanced quotes I tend to escape a last one in order to end the line with quotation mode off, which is not necessary in this particular situation as there is nothing following, but it would be required as soon as there were further commands; for instance `set "quote="^" & set "caret=^"`… – aschipfl Dec 20 '21 at 19:03
  • @jeb Thanks it works and I know how to do it now, but I really don't understand the logic behind it...it seems that cmd string require confusing escaping with instance expansion (like `%cmp%`), but require zero escaping with delayed expansion (`!cmp!`). I hate command lines. Reminds me the [escaping hell of ffmpeg](https://superuser.com/a/1145796/226877) – Byzod Dec 21 '21 at 10:04
  • 1
    @Byzod That's correct, `!cmp!` isn't interpreted anymore, but `%cmp%` is just like typing it directly, for the rules you can read [How does the Windows Command Interpreter (CMD.EXE) parse scripts?](https://stackoverflow.com/a/4095133/463115) – jeb Dec 21 '21 at 12:13