Question about %$Strlen% maco

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Question about %$Strlen% maco

#1 Post by carlsomo » 10 Aug 2013 23:06

Ok, I put this into 'teststrlen.bat' and type at the command line:
C:\>teststrlen This string is 22 long

Code: Select all

@echo off
call :loadMacros
set "myVar=%*"
%$Strlen%  myVar result
echo %myvar% is %result% characters long
exit /b


:loadMacros
set LF=^


::Above 2 blank lines are required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
:::: StrLen String Result
set $StrLen=for %%n in (1 2) do if %%n==2 (%\n%
      for /F "tokens=1,2 delims=, " %%1 in ("!argv!") do (%\n%
         set "str=A!%%~1!"%\n%
           set "len=0"%\n%
           for /l %%A in (12,-1,0) do (%\n%
            set /a "len|=1<<%%A"%\n%
            for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
           )%\n%
           for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~2=%%v") else echo %%v%\n%
      ) %\n%
) ELSE setlocal enableDelayedExpansion ^& set argv=,
exit /b


Fine, it works great. Notice no setlocal statements in the code. Then at the command line:

C:\>teststrlen This string is 22 long
C:\>This string is 22 long is 22 characters long
C:\>set myvar
C:\>myvar=This string is 22 long
C:\>set result
C:\>result=22

Ok fine, so now %$Strlen% is a defined macro available at the command line:

C:\>set $Strlen
displays the macro in all its glory
so try using it from the command line and it bombs:

C:\>%$Strlen% myvar length
...
...etc
C:\>(
set /a "len|=1<<1"
for %B in (!len!) do if "!str:~%B,1!" == "" set /a "len&=~1<<1"
)
8190
C:\>if "!str:~!len!,1!" == "" set /a "len&=~1<<1"

C:\>(
set /a "len|=1<<0"
for %B in (!len!) do if "!str:~%B,1!" == "" set /a "len&=~1<<0"
)
8191
C:\>if "!str:~!len!,1!" == "" set /a "len&=~1<<0"

C:\>endlocal & if "%~b" NEQ "" (set "=!len!" ) else echo !len!
The syntax of the command is incorrect.
=============
at this line of code:

for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~2=%%v") else echo %%v%\n%

%~2 should be 'length' but it is undefined, as is 'myvar'
with 'len' equal to 8191 at that point. len started at 4096 and went up from there.

So why should it work fine from a batch file and not at the command line?

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: Question about %$Strlen% maco

#2 Post by carlsomo » 10 Aug 2013 23:29

Whereas these macros work from the command line:

Code: Select all

@echo off
::   MacroStringLib.bat
::
::   This script installs macros that can be used for string operations
::
::   The script defines the following variables:
::
::     #LF - A variable containing a line feed character
::
::     #CR - A variable containing a carriage return character
::
::     #BS - A variable containing a non-destructive backspace character
::
::     \n - used for specifiying the end of line in a macro definition
::
::     $Eco - A macro used to display a variable with poison characters

if "!" == "" >&2 echo ERROR: Delayed expansion must be disabled when loading %~nx0&exit /b 1

:: Define a Carriage Return string, only useable as !#CR!
for /f %%a in ('copy /Z "%~dpf0" nul') do set "#CR=%%a"

:: Define a Line Feed (newline) string (normally only used as !#LF!)
set #LF=^


:: Above 2 blank lines are required - do not remove

:: Define a newline with line continuation
set ^"\n=^^^%#LF%%#LF%^%#LF%%#LF%^^"

:: $Eco Variable
::
:: Echo's environment variable containing poison characters
:: Environment variables must be passed by name (not reference) and must be assigned
:: A leading '.' and Backspace character are echo'd at the beginning of the string
:: to display leading spaces if they exist; TAKE NOTE IF USING TO WRITE STRING TO FILE
::
set $Eco=@for %%n in (1 2) do @if %%n==2 (%\n%
    @for /f tokens^^=^^1^^*^^ delims^^=^^=^^ eol^^= %%1 in ('set %%#argv%%') do @(%\n%
        @endlocal ^& @set/p=".%#BS%%%2" ^<nul %\n%
    )%\n%
) else @setlocal disabledelayedexpansion ^& @set #argv=

exit /b 0


C:\>call macrostringlib
C:\>set "string=&&&!!^!<{}|?"
C:\>echo %string%
& was unexpected at this time.
C:\>%$Eco% string
&&&!!^!<{}|?
C:\>GetVar.bat string="Enter a poison string: "
Enter a poison string: &&"^&!!^!<>{"}|?%%"^
C:\>%$Eco% string
&&"^&!!^!<>{"}|?%%"^
Last edited by carlsomo on 11 Aug 2013 00:20, edited 1 time in total.

carlsomo
Posts: 91
Joined: 02 Oct 2012 17:21

Re: Question about %$Strlen% maco

#3 Post by carlsomo » 10 Aug 2013 23:55

BTW here is the latest version of GetVar.bat that can be called from command line or bat with or without delayed expansion enabled. I would like to come up with a macro version some day.

Code: Select all

::——————______________________——————::
::                                  ::
::——————|  GetVar  Function  |——————::
::                                  ::
::——————¯¯¯¯¯¯¯ by carl ¯¯¯¯¯¯——————::
::
:: with a lot of help from DosTips.com

:GetVar.bat Var="Enter a variable string: " len /m
@echo off
if "%~1" equ "" goto :Help
if "%~1" equ "/?" goto :Help
SetLocal
set "NotDelayedFlag=!"
if Defined NotDelayedFlag EndLocal&goto :GetVar
EndLocal
:Called with Expansion Enabled
setlocal DisableDelayedExpansion
call :GetVar %*
set/a len=%errorlevel%
if /i %len% equ 0 EndLocal&set "%~1="&exit/b 0
SetLocal EnableDelayedExpansion
set "var=!%~1!"
set "var=!var:^=^^^^!"
set "var=!var:"=""!"
set "var=%var:!=^^^!%" !
set "var=!var:""="!"
for /F "delims=" %%a in (""!var!"") do (
   endlocal
   endlocal
   set "%~1=%%~a" !
   if not "%~3"=="" set/a %~3=%len%
   exit/b %len%
)
goto :eof

:Help
echo(
echo(SYNTAX: GetVar.bat ^<EnVar^> [="User Prompt: "] [length] [/m]
echo(
echo(Assigns User input to the Environment variable passed as 1st argument
echo(Allows unbalance quotes and 'poison' characters mixed with quotes
echo(The only editing key available to the User is 'backspace'
echo(The user string entered is set in the Environment variable, 'Envar'
echo(If no arguments are passed or '/?' this help message is displayed
echo(The fisrt argument is mandatory, the rest are optional
echo(To display a variable with Poison chars and quotes use: Eco.bat
echo(To mask input with '*'s place /m at the very end of the argument list
echo(
echo(Example:
echo(
echo(GetVar.bat Pa$$word="Enter your 'Poison Password' string: " len /m
echo(Eco.bat "Your Poison Password is: '" Pa$$word "' with length: " len
exit/b 0

::——————______________________——————::
::                                  ::
::——————| Subroutine Version |——————::
::                                  ::
::——————¯¯¯¯¯¯¯ by carl ¯¯¯¯¯¯——————::

:GetVar
:: SYNTAX: GetVar ^<EnVar^> [="User Prompt: "] [length] [/m]
::
:: Assigns User input to the Environment variable passed as 1st argument
:: Allows unbalance quotes and 'poison' characters mixed with quotes
:: The only editing key available to the User is 'backspace'
:: The user string entered is set in the Environment variable, 'Envar'
:: If no arguments are passed the routine quits, stand alone version displays help
:: The fisrt argument is mandatory, the rest are optional
:: To display a variable with Poison chars and quotes use: Eco.bat
:: To mask input with '*'s place /m at the very end of the argument list
:: Must be called with delayed expansion disabled for '!', '^' and '&' in string
::
:: Example:
::
:: GetVar.bat Pa$$word="Enter your 'Poison Password' string: " len /m
:: Eco.bat "Your Poison Password is: '" Pa$$word "' with length: " len
:::
::: Dependencies - None           Author: Carl with ideas borrowed from Dostips.com
:::
  if "%~1"=="" goto :End_GetVar
  SetLocal DisableDelayedExpansion
  set "Masked="
  echo(%*|find /i " /m">nul
  if %errorlevel% equ 0 set "Masked=*"
  For /F %%# In ('"Prompt;$H&For %%# in (1) Do Rem"') Do Set "BS=%%#"
  Set "Line="
  if /i "%~2" neq "/m" <Nul set/p=".%BS% %BS%%~2"

:Char_Loop_
  Set "Key="
  For /F "delims=" %%# In (
     'Xcopy /L /W "%~f0" "%~f0" 2^>Nul'
  ) Do If Not Defined Key Set "Key=%%#"
  Set "Key=%Key:~-1%"
  SetLocal EnableDelayedExpansion
  If Not Defined Key ( Rem Enter pressed
     echo(
     If not Defined Line EndLocal&EndLocal&(
        If Defined %~1 Set "%~1="
        If Defined %~3 Set/a "%~3=0
     )&exit/b 0
     For /F delims^=^ eol^= %%# In ("!Line!") Do (
        EndLocal&EndLocal&(
           If not "%~3"=="" Set/a "%~3=%length%"
           If Not "%~1"=="" (Set "%~1=%%#")
           exit/b %length%
        )
     )
  )
  If %BS%==^%Key% (
     Set "Key="
     If Defined Line set/a length-=1& Set "Line=!Line:~0,-1!"& Set /P "=%BS% %BS%" <Nul
  ) Else (
     If defined Masked (Set "Display=*") Else (Set "Display=!Key:~-1!")
     set/a length+=1& Set /p=".%BS%!Display!" <Nul
  )
  If Not Defined Line (
     EndLocal& Set/a length=1& Set "Line=%Key%"
  ) Else For /F delims^=^ eol^= %%# In ("!Line!") Do (
     EndLocal& Set/a length=%length%& Set "Line=%%#%Key%"
  )
  Goto :Char_Loop_
:End_GetVar
EndLocal&exit/b 0

jeb
Expert
Posts: 1042
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Question about %$Strlen% maco

#4 Post by jeb » 11 Aug 2013 02:43

Hi carlsomo,

carlsomo wrote:So why should it work fine from a batch file and not at the command line?

the cause is simple, in the command line the parser works quite different.

- expansions of undefined variables are unchanged instead of beeing removing
- The double percent rule doesn't exist. %% -> %% but in a batch file %%->%
- goto, setlocal and endlocal haven't any effect (I can't find any)
- CALL can only be used for reparsing the line but not with labels

So the important delayed expansion part of the macro can't be enabled.

jeb

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

Re: Question about %$Strlen% maco

#5 Post by penpen » 11 Aug 2013 03:09

I've just noticed, that the $StrLen macro has a slightly bug in this line:

Code: Select all

           for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~2=%%v") else echo %%v%\n%
%%~b is wrong, it should be %%~2.

As far as i know the "setlocal" and "endlocal" instructions do nothing on command line (the same for "goto :label" and ":label"),
although they are recognized as commands, so no error message is displayed:

Code: Select all

Z:\>set Test=1

Z:\>echo %Test%
1

Z:\>setlocal

Z:\>set Test=2

Z:\>echo %Test%
2

Z:\>endlocal

Z:\>echo %Test%
2
So any macro that depends on working "setlocal", "endlocal", "goto :label", and ":label" instructions will not run at the command line as expected.
Or in other words, there is always a command line state that is not working (cmd /E /F /V parameters).

You may add testing code that checks, if the needed state is reachable, and if not, solve it in another way (if possible).
The following should suffice, if run in the macro before prior to the loop:

Code: Select all

:: check if extensions are enabled, store the result in the variable extensionsEnabled
set extensionsEnabled=false
set "extensionsEnabled=true"

:: check if delayed expansion is enabled, store the result in the variable delayedExpansionEnabled
set delayedExpansionEnabled=true
if not "!delayedExpansionEnabled!" == "true" set delayedExpansionEnabled=false

:: check if started from commandline, store the result in the variable runFromCmdLine
set runFromCmdLine=false
goto :label
set runFromCmdLine=true
:label


penpen

Edit: jeb was faster.
Edit2: Corrected the runFromCmdLine (false and true were on the exact false positions)
Edit3: Changed delayedExpansion detection, to make it work on command line, so all should work from command line and batch file now.

Post Reply