Thoughts on this alternative method of obtaining cmdcmdline arguments (safe for all characters?)
Posted: 29 Sep 2019 11:05
After reaching a maximum setlocal recursion level in one of my scripts due to going over a certain number of inputs I decided to re-face the abyss that is batch escaping and getting past endlocal barriers to see if I could find or come up with some alternative method that fulfilled the following aims:
- Is self-contained and requires no temp files or secondary batch scripts
- Works with any input path/filename, regardless of special characters or Unicode.
After trying various things suggested here and elsewhere (SO, etc) it seemed that even if one part of what I tried was successful some other part made it fail (eg: an otherwise working escaped replacement suddenly behaving differently or not working at all when placed in a necessary for loop). Quite frustrating
After racking my head for ideas (not helped by my limited proficiency of batch ) I thought why not instead transport the length of each input past the endlocal barrier instead of the actual string itself, then trim the original argument? It seems that the trim function doesn't interfere with the original characters and so I tried it and to my delight it worked with any input filename I tried!
Now granted I'm not an uber-expert like some here but figured I'd share for thoughts and also in case it's useful for others in a similar situation. There may also be some better method that fulfills those aims that I just didn't find. In the version I made it assumes a cmd.exe path with the /c option and the batch script path in the arguments (ie: the script launched from File Explorer/Send To/Run). I read on SS64.org that these two things are omitted when launched directly from the command line which could be accounted for in with a conditional if/else but I didn't here.
---
Sample filenames to test with (also tested special characters in batch script path and in batch script name). See this post for additional test paths specifically with ampersands in no space paths.
Update: the only character combination so far I've found it can't handle is &( if the path has no other spaces. A limitation of cmd.exe itself (see this post below regarding this). Other combinations of ampersands in no space paths I've tested work fine, including funny enough &).
---
Overview of scripts embedded below and in the following post:
- New-method.bat = the method which splits just the batch script path itself and the input arguments into separate variables
- New-method-with-sub-path-example.bat = same example but showing how sub-paths (eg: %%~pi, %%~ni) can be obtained as well.
- Standard-method-1.bat = typical method of obtaining batch script path and input arguments (using %1, etc). Fails with problematic special characters.
- Standard-method-2.bat = simple cmdcmdline method but not accounting for special characters. Setlocal DisableDelayedExpansion could be used but leads to recursion issues with enough inputs if not paired with an endlocal.
New-method.bat (commented):
- Is self-contained and requires no temp files or secondary batch scripts
- Works with any input path/filename, regardless of special characters or Unicode.
After trying various things suggested here and elsewhere (SO, etc) it seemed that even if one part of what I tried was successful some other part made it fail (eg: an otherwise working escaped replacement suddenly behaving differently or not working at all when placed in a necessary for loop). Quite frustrating
After racking my head for ideas (not helped by my limited proficiency of batch ) I thought why not instead transport the length of each input past the endlocal barrier instead of the actual string itself, then trim the original argument? It seems that the trim function doesn't interfere with the original characters and so I tried it and to my delight it worked with any input filename I tried!
Now granted I'm not an uber-expert like some here but figured I'd share for thoughts and also in case it's useful for others in a similar situation. There may also be some better method that fulfills those aims that I just didn't find. In the version I made it assumes a cmd.exe path with the /c option and the batch script path in the arguments (ie: the script launched from File Explorer/Send To/Run). I read on SS64.org that these two things are omitted when launched directly from the command line which could be accounted for in with a conditional if/else but I didn't here.
---
Sample filenames to test with (also tested special characters in batch script path and in batch script name). See this post for additional test paths specifically with ampersands in no space paths.
Code: Select all
Example [! !§$%&()=`´'_;,.-#+´^ßöäüÖÄÜ°^^#].png
Example [עִבְרִית].png
Example [ファイル名の例].png
Example&Another&&.png
---
Overview of scripts embedded below and in the following post:
- New-method.bat = the method which splits just the batch script path itself and the input arguments into separate variables
- New-method-with-sub-path-example.bat = same example but showing how sub-paths (eg: %%~pi, %%~ni) can be obtained as well.
- Standard-method-1.bat = typical method of obtaining batch script path and input arguments (using %1, etc). Fails with problematic special characters.
- Standard-method-2.bat = simple cmdcmdline method but not accounting for special characters. Setlocal DisableDelayedExpansion could be used but leads to recursion issues with enough inputs if not paired with an endlocal.
New-method.bat (commented):
Code: Select all
@echo off
setlocal enableextensions enabledelayedexpansion
rem New line variable (requires the two empty lines beneath)
set lf=^
set "count=0"
rem Batch script self path
for %%f in ("!cmdcmdline!") do (
setlocal disabledelayedexpansion
set scr="%~f0"
setlocal enabledelayedexpansion
call :len scr scrlen
for /f "delims=" %%a in ("!scrlen!") do endlocal & endlocal & set "scrlen=%%a"
)
rem Trim the cmd.exe path and /c (assumes launched from File Explorer/Send To/Run)
set "cmd=!cmdcmdline:~32!" & call set scr=%%cmd:~0,!scrlen!%%
rem Trim script path and trailing quote from arguments variable
for %%b in (!scrlen!) do set "args=!cmd:~%%b,-1!" & set "args=!args:* =!"
rem Account for input paths both with and without spaces
set args="!args:"=!"
for /f "tokens=1 delims=:" %%d in (!args!) do (
rem Split into new lines based on drive letter
for %%l in ("!lf!") do (
set args=!args:%%d:\=%%l%%d:\!
)
)
set "args=!args: "="!"
rem Remove empty first new line and leading double quote
set "args=!args:~2!"
for %%f in (!args!) do (
set /a "count+=1"
setlocal disabledelayedexpansion
set infull="%%~f"
setlocal enabledelayedexpansion
rem Prepare length
call :len infull infulllen
rem Send past endlocal barrier
for /f "delims=" %%a in ("!infulllen!") do (
endlocal & endlocal
for %%i in (!count!) do set "infulllen[%%i]=%%a"
)
for %%i in (!count!) do (
rem Obtain each input's full path by trimming the full arguments variable using its length value
for %%l in (!infulllen[%%i]!) do set "infull[%%i]=!args:~0,%%l!"
rem Trim space left over from previous argument
set "args=!args:~1!"
rem Trim the same length from !args! after each input, for the next
for %%l in (!infulllen[%%i]!) do set "args=!args:~%%l!"
rem Remove left-over new line character
set "infull[%%i]=!infull[%%i]:~1!"
set "infull[%%i]=!infull[%%i]:"=!"
set infull[%%i]="!infull[%%i]!"
)
)
rem Test output
echo Script path: !scr! & echo.
for /l %%i in (1,1,!count!) do (
echo Input %%i -------------------------------- & echo.
echo !infull[%%i]! & echo. & echo.
)
pause
exit /b
:len
set "s=!%~1!#"
set "len=0"
for %%p in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%p,1!" neq "" (
set /a "len+=%%p"
set "s=!s:~%%p!"
)
)
endlocal
set "%~2=!len!"
exit /b
endlocal