Quotes when starting a batch file

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
jeb
Expert
Posts: 1055
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Quotes when starting a batch file

#1 Post by jeb » 05 Nov 2013 13:59

I hijack the thread CMD: failure of %~d0 when CALL quotes the name of the batch file opened by ChrisJJ.

As this seems to be have other side effects which are too long and not related to the original question.

The main question was, why %~dp0 or any other modifiers fails, when the drive/directory was changed in a batch file AND the batch file itself is started with quotes in the name.

test.bat

Code: Select all

@echo off
setlocal
d:
echo %~f0


Start it with
"TEST.bat"
Output wrote:D:\test.bat


This seems to be a problem, that cmd.exe can start the file, but doesn't remove the quotes correctly.
This can be tested with:

""""TE"S"T.bat"""
Output wrote:D:\"""TE"S"T.bat""


But then I found another effect.

Add another file into the same directory
**ExtraRetest.bat**

Code: Select all

@echo off
echo *** "%~0" ***


And now start your test.bat with (Yes, there is a redirection character) :!:
"<test.bat"
Output wrote:*** "<test.bat" ***


The output is obviously not from test.bat, it's from ExtraRetest.bat :!:

This is really strange :?:

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Quotes when starting a batch file

#2 Post by dbenham » 05 Nov 2013 19:24

:shock: How in hell did you stumble on that :!:

Amazing and totally unexpected :)

It turns out that > also functions as a wildcard. I tested on both XP and Win 7.
The rules below are not quite correct. See penpen's post and my response below

Code: Select all

Match 0 or more characters:   *   (can match dots)
                              <   (cannot match dots)
------------------------------------------------------
    Match 0 or 1 character:   ?   (cannot match dots)
                              >   (cannot match dots)

All the above wildcards work in commands like DIR and COPY etc. The > and < symbols can be quoted or escaped if used within a parameter.

Only < and > can be used within a command name, and only if quoted - escaping will not work.
When a command matches multiple files, the first one found is executed.
The < and > wildcards can only be used in the command name, never the command path.

I created two bat files in my C:\test folder with the same content: weird1x.bat and weird2.bat

Code: Select all

@echo Excecuting %~f0  (%0)

Here are some interesting tests with results:

Code: Select all

C:\test>dir /b *ei*
weird1x.bat
weird2.bat

C:\test>dir /b "<ei<"
File Not Found

C:\test>dir /b "<ei<.<"
weird1x.bat
weird2.bat

C:\test>dir /b "?ei???????"
File Not Found

C:\test>dir /b "?ei???.???"
weird2.bat

C:\test>dir /b "?ei????.???"
weird1x.bat
weird2.bat

C:\test>dir /b ">ei>>>>>>>"
File Not Found

C:\test>dir /b ">ei>>>.>>>"
weird2.bat

C:\test>dir /b ">ei>>>>.>>>"
weird1x.bat
weird2.bat

C:\test>dir /b ">ei>>>>.>>"
File Not Found

C:\test>dir /b ">ei>>>>.<"
weird1x.bat
weird2.bat

C:\test>dir /b ^<ei^<.^<
weird1x.bat
weird2.bat

C:\test>type *ei*

weird1x.bat


@echo Excecuting %~f0  (%0)

weird2.bat


@echo Excecuting %~f0  (%0)

C:\test>type "<ei<.<" & rem Note how file name headers are missing
@echo Excecuting %~f0  (%0)
@echo Excecuting %~f0  (%0)

C:\test>copy "<ei>>>.>>>" ">.copy"
The filename, directory name, or volume label syntax is incorrect.
        0 file(s) copied.

C:\test>copy "<ei>>>.>>>" *.copy & dir /b *.copy & del *.copy
        1 file(s) copied.
weird2.copy

C:\test>copy ^<ei^>^>^>.^>^>^> *.copy & dir /b *.copy & del *.copy
        1 file(s) copied.
weird2.copy

C:\test>*ei*.bat
'*ei*.bat' is not recognized as an internal or external command,
operable program or batch file.

C:\test>"*ei*.bat"
'"*ei*.bat"' is not recognized as an internal or external command,
operable program or batch file.

C:\test>^<ei^<.^<
The syntax of the command is incorrect.

C:\test>"<ei<.<"
Excecuting C:\test\weird1x.bat  ("<ei<.<")

C:\test>"<ei>>>>.<"
Excecuting C:\test\weird1x.bat  ("<ei>>>>.<")

C:\test>"<ei>>>.<"
Excecuting C:\test\weird2.bat  ("<ei>>>.<")

C:\test>"c:\test\<ei<.<"
Excecuting c:\test\weird1x.bat  ("c:\test\<ei<.<")

C:\test>"c:\t>st\<ei<.<"
The filename, directory name, or volume label syntax is incorrect.

C:\test>


Dave Benham
Last edited by dbenham on 06 Nov 2013 19:51, edited 1 time in total.

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Quotes when starting a batch file

#3 Post by penpen » 06 Nov 2013 13:26

It is not fully true, that the '<' character cannot match dots (win 7):

Code: Select all

Z:\a>dir /b
a
a.txt

Z:\a>dir "<" /b
a

Z:\a>dir "<<" /b
a
a.txt

Z:\a>dir "a<txt" /b
a.txt

Z:\a>

penpen

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Quotes when starting a batch file

#4 Post by dbenham » 06 Nov 2013 19:50

Good catch penpen :)

Based on some more experiments, < can match any set of characters (including dots) in the base name, or it can match any set of characters (including the leading dot) in the extension. But a single < cannot match characters from both the base name and the extension.

The > can match any character (including dot) anywhere in the name, except it cannot match the leading dot of the extension.

I created c:\test\abc.123.xyz.txt

Code: Select all

C:\test>dir /b "abc>>>>>>>>>>>>"
File Not Found

C:\test>dir /b "abc>>>>>>>>.>>>"
abc.123.xyz.txt

C:\test>dir /b "abc<"
File Not Found

C:\test>dir /b "abc<<"
abc.123.xyz.txt

C:\test>


Dave Benham

Squashman
Expert
Posts: 4486
Joined: 23 Dec 2011 13:59

Re: Quotes when starting a batch file

#5 Post by Squashman » 07 Nov 2013 18:18

I am beginning to think they were smokin alot more then Peyote in those early days in New Mexico.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Quotes when starting a batch file

#6 Post by dbenham » 07 Nov 2013 19:11

So here is a summary of the updated wild card rules:

Code: Select all

       Function              |  Restrictions                 | Wildcard
-----------------------------+-------------------------------+----------
Match 0 or more characters   | No restrictions               |    *
                             +-------------------------------+----------
                             | Cannot match characters from  |    <
                             | both base name and extension. |
                             | Extension dot is considered   |
                             | to be part of base name.      |
-----------------------------+-------------------------------+----------
Match exactly 1 character or | Cannot match dot in extension |    ?
match 0 characters if at end |                               |    >
of name or end of extension  |                               |
-----------------------------+-------------------------------+----------


Dave Benham

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Quotes when starting a batch file

#7 Post by dbenham » 07 Nov 2013 22:54

Returning to the original effect regarding quotes around the command.

jeb wrote:This seems to be a problem, that cmd.exe can start the file, but doesn't remove the quotes correctly.

I'm not convinced. Enclosing quotes without the extension seems to be a special case. Internal quotes without enclosing quotes don't cause a problem.

C:\test\test.bat

Code: Select all

@echo off
setlocal
echo      raw: %%0    = %0
echo     main: %%~tf0 = %~tf0
call :getPath
exit /b

:getPath
echo function: %%~tf0 = %~tf0

No quotes works fine:

Code: Select all

C:\test>TEST
     raw: %0    = TEST
    main: %~tf0 = 11/07/2013 11:05 PM C:\test\test.bat
function: %~tf0 = 11/07/2013 11:05 PM C:\test\test.bat

Enclosing quotes fails:

Code: Select all

C:\test>"TEST"
     raw: %0    = "TEST"
    main: %~tf0 = C:\test\TEST
function: %~tf0 = 11/07/2013 11:05 PM C:\test\test.bat

Internal quotes works fine:

Code: Select all

C:\test>T"ES"T
     raw: %0    = T"ES"T
    main: %~tf0 = 11/07/2013 11:05 PM C:\test\test.bat
function: %~tf0 = 11/07/2013 11:05 PM C:\test\test.bat

Enclosing quotes with full name works fine as long as working directory not changed:

Code: Select all

C:\test>"TEST.BAT"
     raw: %0    = "TEST.BAT"
    main: %~tf0 = 11/07/2013 11:05 PM C:\test\test.bat
function: %~tf0 = 11/07/2013 11:05 PM C:\test\test.bat


And now for some real fun :) I'll combine the quote problem with the newly discovered wildcard behavior. The quoted value containing wildcards can report the wrong file if the file is added after the script has begun :!:

C:\test\z2.bat

Code: Select all

@echo off
echo %0
echo %~f0
copy nul z1.bat >nul
echo %~f0
call :getPath
del z1.bat
exit /b

:getPath
echo %~f0
exit /b

--OUTPUT--

Code: Select all

C:\test>"Z>.bat"
"Z>.bat"
C:\test\z2.bat
C:\test\z1.bat
C:\test\z2.bat


Another obscure problem is the issue of names containing ! and/or ^ while delayed expansion is enabled.

I've written a robust :currentScript function that should always retrieve the correct values, no matter what the circumstances. The first argument is required - the name of the variable to store the result. The second parameter is optional - a string of modifiers, without the tilde. The default modifier is F (equivalent to DPNX).

The first part of the script is a test harness for the function. It tests the function under various scenarios. I intentionally gave the script a crazy name containing characters that don't play nice with delayed expansion.

C:\test\test^it!.bat

Code: Select all

@echo off
setlocal disableDelayedExpansion
set arg0=%0
if "%arg0:~0,1%%arg0:~0,1%" equ """" (set "arg0=Quoted") else set "arg0=Unquoted"

call :header
echo                %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "%rtn%"

setlocal enableDelayedExpansion
call :header
echo                %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "!rtn!"

endlocal
d:
call :header
echo                %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "%rtn%"

setlocal enableDelayedExpansion
call :header
echo                %%~tzf0 = "%~tzf0"
call :currentScript rtn tzf
echo :currentScript result = "!rtn!"

exit /b
 
:header
set "rtn="
setlocal
echo(
echo(
if "!" equ "" (set "delayed=ON") else set "delayed=OFF"
if "%cd%\" equ "%~dp0" (set "cwd=Original") else set "cwd=Modified"
echo %arg0%: %cwd% working directory, Delayed expansion = %delayed%
echo ---------------------------------------------------------------------------
exit /b

 
:currentScript  rtnVar  [options]
setlocal
set "notDelayed=!"
setlocal disableDelayedExpansion
set "options=%~2"
if not defined options set "options=f"
call set "rtn=%%~%options%0"
if not defined notDelayed set "rtn=%rtn:^=^^%"
if not defined notDelayed set "rtn=%rtn:!=^!%"
endlocal & endlocal & set "%~1=%rtn%" !
exit /b

Here are the results using both an unquoted and a quoted call:

Code: Select all

C:\test>TEST^^IT!


Unquoted: Original working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
               %~tzf0 = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"


Unquoted: Original working directory, Delayed expansion = ON
---------------------------------------------------------------------------
               %~tzf0 = "11/07/2013 10:43 PM 1397 C:\test\testit.bat"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"


Unquoted: Modified working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
               %~tzf0 = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"


Unquoted: Modified working directory, Delayed expansion = ON
---------------------------------------------------------------------------
               %~tzf0 = "11/07/2013 10:43 PM 1397 C:\test\testit.bat"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"

C:\test>"TEST^IT!"


Quoted: Original working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
               %~tzf0 = "C:\test\TEST^IT!"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"


Quoted: Original working directory, Delayed expansion = ON
---------------------------------------------------------------------------
               %~tzf0 = "C:\test\TESTIT"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"


Quoted: Modified working directory, Delayed expansion = OFF
---------------------------------------------------------------------------
               %~tzf0 = "D:\TEST^IT!"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"


Quoted: Modified working directory, Delayed expansion = ON
---------------------------------------------------------------------------
               %~tzf0 = "D:\TESTIT"
:currentScript result = "11/07/2013 10:43 PM 1397 C:\test\test^it!.bat"

C:\test>

I also tested the function with batch files named test^it.bat, test!.bat, and test.bat, and all worked as expected (not shown)


Dave Benham

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Quotes when starting a batch file

#8 Post by penpen » 10 Nov 2013 08:27

dbenham wrote:
jeb wrote:This seems to be a problem, that cmd.exe can start the file, but doesn't remove the quotes correctly.

I'm not convinced. Enclosing quotes without the extension seems to be a special case. Internal quotes without enclosing quotes don't cause a problem.
I agree beeing not convinced, but I think the special case is %0 without enclosing quotes, as its behaviour differs from all other cases.
Regarding to the help pages of for and call the behavior of the %0 case is unexpected, as it only should add the current volume descriptor, the current path, the given name, and the given extension:

Code: Select all

:: test.bat
@echo off
setlocal
echo     main: %%0    = %0
echo     main: %%1    = %1
echo     main: %%~tzf0 = %~tzf0
echo     main: %%~tzf1 = %~tzf1
echo(
call :getPath :getPath

setlocal enableDelayedExpansion
set "__a=%%%%a"
set "__tzfa=%%%%~tzfa"

for %%a in (%0) do (
echo      for: !__a!    = %%a
echo      for: !__tzfa! = %%~tzfa
)

endlocal
exit /b

:getPath
echo function: %%0    = %0
echo function: %%1    = %1
echo function: %%~tzf0 = %~tzf0
echo function: %%~tzf1 = %~tzf1
echo(

Result:

Code: Select all

Z:\>test test
    main: %0    = test
    main: %1    = test
    main: %~tzf0 = 10.11.2013 15:10 4408 Z:\test.bat
    main: %~tzf1 = Z:\test

function: %0    = :getPath
function: %1    = :getPath
function: %~tzf0 = 10.11.2013 15:10 4408 Z:\test.bat
function: %~tzf1 = Z:\:getPath

     for: %%a    = test
     for: %%~tzfa = Z:\test

Z:\>test.bat test.bat
    main: %0    = test.bat
    main: %1    = test.bat
    main: %~tzf0 = 10.11.2013 15:10 4408 Z:\test.bat
    main: %~tzf1 = 10.11.2013 15:10 4408 Z:\test.bat

function: %0    = :getPath
function: %1    = :getPath
function: %~tzf0 = 10.11.2013 15:10 4408 Z:\test.bat
function: %~tzf1 = Z:\:getPath

     for: %%a    = test.bat
     for: %%~tzfa = 10.11.2013 15:10 4408 Z:\test.bat

Z:\>"test" "test"
    main: %0    = "test"
    main: %1    = "test"
    main: %~tzf0 = Z:\test
    main: %~tzf1 = Z:\test

function: %0    = :getPath
function: %1    = :getPath
function: %~tzf0 = 10.11.2013 15:10 4408 Z:\test.bat
function: %~tzf1 = Z:\:getPath

     for: %%a    = "test"
     for: %%~tzfa = Z:\test

Z:\>"test.bat" "test.bat"
    main: %0    = "test.bat"
    main: %1    = "test.bat"
    main: %~tzf0 = 10.11.2013 15:10 4408 Z:\test.bat
    main: %~tzf1 = 10.11.2013 15:10 4408 Z:\test.bat

function: %0    = :getPath
function: %1    = :getPath
function: %~tzf0 = 10.11.2013 15:10 4408 Z:\test.bat
function: %~tzf1 = Z:\:getPath

     for: %%a    = "test.bat"
     for: %%~tzfa = 10.11.2013 15:10 4408 Z:\test.bat

Z:\>
So %f0 "fails" with no doublequotes around the command.
But i see this case as a special service, so it doesn't bother me.

penpen

Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

Re: Quotes when starting a batch file

#9 Post by Liviu » 08 May 2014 11:06

dbenham wrote:Enclosing quotes without the extension seems to be a special case.

Indeed. And that special case breaks self-referential batch code that relies on "%~f0" at the top level, such as:
- for/f rereading in order to display embedded help;
- relaunching "%~f0" in recursive or hybrid scenarios;
- defining CR using the copy/z construct - lest end up with "!CR!"=="The" from "The system cannot find ..." ;-)

Only recourse seems to be to guard any self-reference inside a called :label, where "%~f0" expands correctly, as noted above.

Code: Select all

@echo off
if "%~x0"=="" (call :self & goto :eof)
:self
echo callee: %0 "%~f0" "%~dpnx0"
Or, of course, require calling code to always specify an extension when quoting the target pathname.

Liviu

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Quotes when starting a batch file

#10 Post by dbenham » 23 Jan 2015 16:11

The discussion about undocumented wild cards < and > is continued at viewtopic.php?f=3&t=6207


Dave Benham

Post Reply