Get arguments without temporary file

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

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

Get arguments without temporary file

#1 Post by jeb » 22 May 2018 07:17

Hi,

after the claim of Jan Antonin, I take a closer look at the problem and I found a nearly simple method
Jan Antonin wrote:My biggest (not from the pragmatic point of view of course) contribution which I am really proud of will be an ultimate method of obtainig all command line arguments of a sript inside from the script of course. With no compromise, without any temp file. And storing these arguments inside a normal named variable like %myVar%, not %1.
I dont care about the speed (I care but there is no principal difference in a speed - the upper bound of time is polynomial depending on a lenght of an input :-) )

Code: Select all

@echo off
setlocal EnableDelayedExpansion
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F

:getParam
(set LF=^
%=empty=%
)

> nul break | (%~d0\:GetParams:\..\%~pnx0
REM %%LF%% )
REM ###,%*,# %%LF%%
)

for /F "delims=" %%L in ('doskey /macros ^|findstr /R "^return="') do (
    set "cmdLine=%%L"
)
set "args=!cmdLine:*###,=!"
set "args=!args:""="!"
set "args=!args:$$=$!"
set "args=!args:~0,-10!"
doskey return=

echo args: --- !args! ---
exit /b

:GetParams
rem echo !cmdcmdline!
set "line=!cmdcmdline!"
set "line=!line:"=""!"
set "line=!line:$=$$!"
doskey return=!line!
exit
This uses a sub context of cmd.exe by using a pipe.
Former I was aware that you can access the arguments there, but I wasn't able to return the values back to the parent process.

First I played a bit with splitting the cmdcmline into single chars and tranfser each char via the exitcode, but it's slow and it has the disadvantage that each character has to be identified and converted to the ASCII value.

Then I remembered the doskey technic to return values from a child process, that's all.

Many thanks to Jan Antonin, without you I would have already give up :D
I'm still eager awaiting your solution

jeb

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

Re: Get arguments without temporary file

#2 Post by penpen » 22 May 2018 10:00

Sad to say, that the first argument i tested gave me this:

Code: Select all

Z:\>test.bat "a!b!c!d!e"
args: --- "ace" ---
(Because of enabled delayed expansion i wanted to test that first; but anything else i tested worked correct; but i haven't yet tested doublequotes and circumflex accent.)


penpen

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

Re: Get arguments without temporary file

#3 Post by jeb » 22 May 2018 11:51

Hi penpen,

really stupid bug. I simply forgot the exclamation marks.

It can be fixed by moveing the setlocal EnabledelayedExpansion behind the "pipe-block".

Code: Select all

@echo off
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F

:getParam
setlocal DisableDelayedExpansion
(set LF=^
%=empty=%
)

> nul break | (%~d0\:GetParams:\..\%~pnx0
REM %%LF%% )
REM ###,%*,# %%LF%%
)

for /F "delims=" %%L in ('doskey /macros ^|findstr /R "^return="') do (
    set "cmdLine=%%L"
)
setlocal EnableDelayedExpansion
set "args=!cmdLine:*###,=!"
set "args=!args:""="!"
set "args=!args:$$=$!"
set "args=!args:~0,-10!"
doskey return=

echo args: --- !args! ---
exit /b

:GetParams
setlocal EnableDelayedExpansion
rem echo !cmdcmdline!
set "line=!cmdcmdline!"
set "line=!line:"=""!"
set "line=!line:$=$$!"
doskey return=!line!
exit
I suppose it's stable against any other special characters.

Line feeds and carriage returns doesn't work.

siberia-man
Posts: 208
Joined: 26 Dec 2013 09:28
Contact:

Re: Get arguments without temporary file

#4 Post by siberia-man » 22 May 2018 12:41

Time ago I was a bit angry on the algorithm parsing command line arguments in batch scripts. I made few attempts to implement something like getopts in Bash or GetOptions from the Perl's modules Getopts::Long or Getopts::Std. Today I decided to make some rough example to parse and count command line arguments and to enable all possible symbols as much as possible. I am not sure that this attempt fits the actual thread but (in my opinion) it very close to the topic of the discussion. Of course, this example requires to be polished so much to make it presentable and usable :)

The only limitation I found is the caret symbol that must be duplicated!

Code: Select all

@echo off

set /a "OPTIND=0"

:getopt.1
if "%~1" == "" if [%1] == [] goto :getopt.2

set /a OPTIND=OPTIND+1

set "OPT.%OPTIND%=%~1"
shift /1

setlocal enabledelayedexpansion
echo:=== [!OPT.%OPTIND%!] ===
endlocal

goto :getopt.1
:getopt.2


for /l %%L in ( 1, 1, %OPTIND% ) do (
	call echo:=== %%L: [%%OPT.%%L%%] ===
)

echo:Total: %OPTIND%

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

Re: Get arguments without temporary file

#5 Post by jeb » 22 May 2018 12:48

I build a small unittest with different more or less complex arguments.

Code: Select all

@echo off
setlocal DisableDelayedExpansion
set "destBatch=GetParam.bat"

set "arg1=simple text"
set "arg2=special unquoted &|<>%%!^#"
set ^"arg3=special quoted "&|<>%%!^#""
set "arg4=&"^&"
set "arg5=caret^"caret2^^.."
set "arg6=^^!"


for /L %%n in (1 1 6) do (
	call :testArgument arg%%n
)

exit /b

:testArgument
setlocal EnableDelayedExpansion
echo(
set "argTest=!%1!"
echo Test %1=!argTest!
set "argTest=!argTest:^=^^!"
set "argTest=!argTest:&=^&!"
set "argTest=!argTest:|=^|!"
set "argTest=!argTest:<=^<!"
set "argTest=!argTest:>=^>!"
set ^"argTest=!argTest:"=^"!"
setlocal DisableDelayedExpansion
cmd /c %destBatch% %%argTest%%
endlocal
endlocal
exit /b
@siberia-man:
The first test works with your code, the others fail :wink:

siberia-man
Posts: 208
Joined: 26 Dec 2013 09:28
Contact:

Re: Get arguments without temporary file

#6 Post by siberia-man » 22 May 2018 14:28

@jeb:

I think you are on the right way. But I can't understand why you parse the options before launching the test script? Why you don't use it simply calling like GetParam.bat %*?

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

Re: Get arguments without temporary file

#7 Post by jeb » 22 May 2018 15:04

siberia-man wrote:
22 May 2018 14:28
I think you are on the right way. But I can't understand why you parse the options before launching the test script? Why you don't use it simply calling like GetParam.bat %*?
The problem for the unittest is the fact, that I don't know a simple way to show the argument that will be used and use that same argument to call a secondary batch file.

Think of the simple test case of a single ampersand, if I want to test it from the command line I have to use

Code: Select all

C:\> getParam ^&
Without the caret it simply fails.
With quotes or carets it gets only more annoying.

In the unittest, the arguments are first assigned to a variable (arg1 .. arg6).
They contain exactly the content that can be echoed with delayed expansion.
And it's the content the tested batch file (getParam.bat in my case) should fetch from %*.

Simply calling the test batch with CALL will produce really hard problems, as CALL itself doubles carets and gets ugly with the parsing phases.
So I used cmd /c, there I "only" need to escape all special characters before starting the batch file.

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Get arguments without temporary file

#8 Post by Ed Dyreen » 22 May 2018 18:52

Code: Select all

@echo off &ver &cd
call tst.cmd "a!b!c!d!e"
pause
exit

Code: Select all

Microsoft Windows XP [versie 5.1.2600]
W:\ED\VIP\PROJ\DEV\doskit\doskitXPx86 v20180426\Hello world !
W:\:GetParams:\..\\ED\VIP\PROJ\DEV\doskit\doskitXPx86 wordt niet herkend als een interne
of externe opdracht, programma of batchbestand.
args: ---  ---
Druk op een toets om door te gaan. . .
It doesn't seem to like spaces in current directory. :(

Code: Select all

@echo off

for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F

:getParam
setlocal DisableDelayedExpansion
(set LF=^
%=empty=%
)

> nul break | ("%~d0\:GetParams:\..\%~pnx0"
REM %%LF%% )
REM ###,%*,# %%LF%%
)

for /F "delims=" %%L in ('doskey /macros ^|findstr /R "^return="') do (
    set "cmdLine=%%L"
)
setlocal EnableDelayedExpansion
set "args=!cmdLine:*###,=!"
set "args=!args:""="!"
set "args=!args:$$=$!"
set "args=!args:~0,-10!"
doskey return=

echo args: --- !args! ---
exit /b

:GetParams
setlocal EnableDelayedExpansion
rem echo !cmdcmdline!
set "line=!cmdcmdline!"
set "line=!line:"=""!"
set "line=!line:$=$$!"
doskey return=!line!
exit
Stupid jeb code, we always needs to fix stupid jeb code :evil:

Code: Select all

Microsoft Windows XP [versie 5.1.2600]
W:\ED\VIP\PROJ\DEV\doskit\doskitXPx86 v20180426\Hello world !
args: --- "a!b!c!d!e" ---
Druk op een toets om door te gaan. . .
This does seem to be fast and bulletproof, until now I've been using the much simpler

Code: Select all

(call set $args=%%^*) %= beware that arg * can break code between braces unless called =%
%= $lf prevents 'input corrupt or incorrectly escaped' exception =%
(set "$args=%$args%" !)%= retract carrets doubled by call =%
Where the %^* is a leftover from a bugfix for Quick Batch File Compiler that didn't expand %* properly.

I think I will adopt jeb's method 8)

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

Re: Get arguments without temporary file

#9 Post by jeb » 22 May 2018 23:46

Ed Dyreen wrote:
22 May 2018 18:52
It doesn't seem to like spaces in current directory. :(
Ed Dyreen wrote:
22 May 2018 18:52
Stupid jeb code, we always needs to fix stupid jeb code :evil:
Yes, and I'm the first one who criticize such sloppy code :(

But beside that bugs I tested with line feeds and it suprises me, as line feeds are converted to ampersands.

Code: Select all

@ech off
setlocal EnableDelayedExpansion
(set LF=^
%=%
)
set ^"arg3=^^^^^"Line1^^^^!LF!!LF!line2"" 
call GetParam.bat %%arg3%%
Output wrote:args: --- "Line1 & line2" ---
But the problem with line feeds is that the content after the first line feed will be parsed and has to be valid batch syntax.
This one fails with:
")" can't be processed ...

Code: Select all

set ^"arg3=^^^^^"Line1^^^^!LF!!LF! ()"" 

Jan Antonin
Posts: 9
Joined: 20 May 2018 13:53

Re: Get arguments without temporary file

#10 Post by Jan Antonin » 23 May 2018 03:32

I have to admitt that I strongly underestimated a complexity of this task hidden in details.
It has already taken me too much time so a moment to public even a torso comes.
I still have a strong feeling I could be able to fill all remainig holes but instead of that I will use a beautiful fuzzy and foggy phrazeology among which at the top of the list and the cutest one is "Without loss of generality let's assume ... " :-)
The problem is that from one moment it is not fun for me but a nightmare. I dont enjoy thinking about it anymore.

1)
An assumption that make my solution not general in an ultimate meaning and which I am not able to eliminate:

Arguments (which we are trying to get) must not contain a name of a variable in percent signs which contains "the tricky LF char".

So if an argument is

Code: Select all

abcdef%LF%ghij
it is a problem

2)
An assumption is not a problem IMHO:
given arguments will be obtained in a "canonical" form:
"#data with every double quotation mark being doubled#"
e.g. if an argument is

Code: Select all

asdf^"^y/" ""ki!jk^fgb%jfhf"fg
then a result in a canonical form will be

Code: Select all

"#asdf^""^y/"" """"ki!jk^fgb%jfhf""fg#"
3) other assumtions which are not important and which be mentioned later

the torso of a script follows in the next post

Jan Antonin
Posts: 9
Joined: 20 May 2018 13:53

Re: Get arguments without temporary file

#11 Post by Jan Antonin » 23 May 2018 04:24

It is a short verion without a "selfpiping" e.g. the result will be only displayed.
It is for the first argument %1. I am not sure whether using %* instead of %1 does or does not bring other problems.

Code: Select all

@setlocal ENABLEEXTENSIONS
@setlocal DISABLEDELAYEDEXPANSION
@prompt -$G$S
@rem I just like this prompt, it plays no rule at all

@set "xFullpathScriptQuotNo_=%~f0"
@for /F "usebackq tokens=*" %%A in (`attrib "%~dp0."`) do @set "xFullpathDirQuotNo_=%%~fA"
@set Qu=^"
@rem will be used for piping in a future improved version

@cd "%xFullpathDirQuotNo_%"
@set LF=^


@rem it is not possible to delete two previous empty lines
@echo on

rem %1
rem version A
@(rem %1#
%%LF%%echo H"#%%cmdcmdline:"=""%%#"H
)|find /V /N ""

rem version B
@set ohce=^

echo "
@rem it is not possible to delete two previous lines

@(rem %1#
%%ohce%%%%cmdcmdline:"=""%%"
)|find /V /N ""

rem version C
@(
set anotherVar=H"#%%cmdcmdline:"=""%%H"Z"
"%%LF%%rem %1#
%%LF%% set anotherVar
)|find /V /N ""

pause
@rem the end

Jan Antonin
Posts: 9
Joined: 20 May 2018 13:53

Re: Get arguments without temporary file

#12 Post by Jan Antonin » 23 May 2018 04:38

It is only a couple of lines, I would rather never say the time it has taken.
Because of that I am delayed with all my personal an profesional tasks, it was kind of an obssesion or what.

I may react with a long delays for your posts, if ever, sorry.

Good luck guys dealing with all this "not the best concept of everything" in our lifes and world!!

Jan Antonin

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

Re: Get arguments without temporary file

#13 Post by Squashman » 23 May 2018 07:01

Jan, when you say "torso" I think you mean to say "body".

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: Get arguments without temporary file

#14 Post by Ed Dyreen » 23 May 2018 07:35

I tested

Code: Select all

@echo off &setlocal enableDelayedExpansion

(set LF=^
%=empty=%
)

set "arg=simple text"
set arg &call tst.cmd %%arg%%

set "arg=special unquoted &|<>%%!^#"
set arg &call tst.cmd %%arg%%

set ^"arg=special quoted "&|<>%%!^#""
set arg &call tst.cmd %%arg%%

set "arg=&"^&"
set arg &call tst.cmd %%arg%%

set "arg=caret^"caret2^^.."
set arg &call tst.cmd %%arg%%

set "arg=^^!"
set arg &call tst.cmd %%arg%%

set ^"arg=^^^^^"Line1^^^^!LF!!LF!line2""
set arg &call tst.cmd %%arg%%

pause
exit
only to find that

Code: Select all

arg=special unquoted &|<>%#
| niet verwacht op dit moment.
And then I realised jeb's batch wasn't even called because call tst.cmd special unquoted &|<>%%!^# would obviously crash.

And then I wondered why in gods name would one want to be able to retrieve all characters raw ?

Already the receiving batch won't handle CR and LF but on top of that parameters can not just be hand over like or they are meaningless for example: ( call tst.cmd special unquoted &|<>%%!^# ) I simply don't see the benefit of being able to handle corrupt data. I say corrupt because call should be at least like this ( call tst.cmd "special unquoted &|<>%%!^#" ). It is the programmers responsibility to correctly call functions, why would a function need to be able to handle grave misuse ?

If you have Special Data, Why not just pass this data by reference ? What's the point of all this ?

Yes I also have a stringReplace function and yes it will accept arguments byValue to some extend but at least it has some rules, arguments need to be quoted for example and if quoted they are always byVals. I would need to know at least what calls are considered legal and which are not. Because obviously call batch |& will never work.

Enlighten me, thanks

Aacini
Expert
Posts: 1885
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: Get arguments without temporary file

#15 Post by Aacini » 23 May 2018 08:03

Ed Dyreen wrote:
23 May 2018 07:35
If you have Special Data, Why not just pass this data by reference ? What's the point of all this ?

Yes I also have a stringReplace function and yes it will accept arguments byValue to some extend but at least it has some rules, arguments need to be quoted for example and if quoted they are always byVals. I would need to know at least what calls are considered legal and which are not. Because obviously call batch |& will never work.

Enlighten me, thanks
I agree... :D

Antonio

Post Reply