foolproof counting of arguments
Moderator: DosItHelp
-
- Posts: 240
- Joined: 04 Mar 2014 11:14
- Location: germany
Re: foolproof counting of arguments
instead of start "" / b ...
what happens if you
do it without /b?
Phil
what happens if you
do it without /b?
Phil
Re: foolproof counting of arguments
Nice trick jeb
The main thread can be terminated, but this is reasonable only when batch file called from command line and not from another batch file.
The trick is to redirect the stdin to a write-only stream, when it backs to prompt it can't read from stdin and will be terminated immediately and the handle to params.tmp will be closed then it can be deleted in StayAlive.
Code: Select all
@echo off
REM *** Thread redirector
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F
cls
REM *** Clear params.tmp
break > params.tmp
start "" /b cmd /c "%~d0\:StayAlive:\..\%~pnx0" params.tmp
(set LF=^
%=empty=%
)
REM *** Change prompt for better recognition
prompt #PROMPT#
REM *** Change streams permanently
REM *** stream1 redirects to params.tmp
REM *** stream2 redirects to nul
;;;;; echo on >nul 2>nul 0>nul 3>params.tmp 4>nul 5>&3
@REM *** This is the magic part, it forces a syntax error, the error message itself shows the expanded %asterix without ANY modification
( Prepare ) PARAMS:%LF%%*%LF%
echo Works
exit /b
REM *** Second thread to fetch and show the parameters
:StayAlive
:__WaitForParams
if %~z1 EQU 0 (
goto :__WaitForParams
)
REM *** Show the result
findstr /n "^" %1
del %1
echo I'm alive, parent dead :(
pause
exit
Re: foolproof counting of arguments
It opens a new cmd window, but that doesn't solve the problem that the main thread is still open.pieh-ejdsch wrote: ↑28 Aug 2018 10:27instead of start "" / b ...
what happens if you
do it without /b?
That's a cool trick, I didn't know.sst wrote: ↑29 Aug 2018 16:26The main thread can be terminated, but this is reasonable only when batch file called from command line and not from another batch file.
The trick is to redirect the stdin to a write-only stream, when it backs to prompt it can't read from stdin and will be terminated immediately and the handle to params.tmp will be closed then it can be deleted in StayAlive.
I changed the start cmd /c to cmd /k and I removed the exit from the stayAlive thread
That can be used to build a more "useable" solution.
Code: Select all
@echo off
REM *** Thread redirector
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F
REM *** Clear params.tmp
break > params.tmp
start "" /b cmd /k "%~d0\:StayAlive:\..\%~pnx0 params.tmp"
(set LF=^
%=empty=%
)
REM *** Change prompt for better recognition
prompt #PROMPT#
REM *** Change streams permanently
REM *** stream1 redirects to params.tmp
REM *** stream2 redirects to nul
echo on >nul 2>nul 0>nul 3>params.tmp 4>nul 5>&3
@REM *** This is the magic part, it forces a syntax error, the error message itself shows the expanded %asterix without ANY modification
( Prepare ) PARAMS:%LF%%*%LF%
echo Works
exit /b
REM *** Second thread to fetch and show the parameters
:StayAlive
:__WaitForParams
if %~z1 EQU 0 (
goto :__WaitForParams
)
REM *** Show the result
findstr /n "^" %1
output wrote:c:\temp\Parser>GetParamFull.bat Line1^
Mehr?
Mehr? () Line2^
Mehr?
Mehr? caret^^ Line3
1:
2:#PROMPT#( Prepare ) PARAMS:
3:Line1
4:() Line2
5:caret^ Line3
6:
7:
8:#PROMPT#
c:\temp\Parser>
Re: foolproof counting of arguments
Very cool technique with surprising behavior.
It is very odd that the extra label is required. I get the wrong prompt in the output if I consolidate the thread redirection label and loop label into a single label.
I extended the method to capture the binary image of the parameter string in an ARGS variable via CERTUTIL.
As written, the code clobbers variables L R B C and CHAR, in addition to setting ARGS. I suppose one of the safe return techniques can be used to make those variables temporary.
There is still one nasty potential problem with the technique - It will fail miserably if any of the streams 3 and/or 4 and/or 5 are already defined when the script is called due to prior redirection.
If I can determine the lowest 3 available streams, then the technique could be modified to always work as long as there are 3 available streams within the range 3 - 9. I think I know how to do it, but it will have undesired screen output that I don't know how to avoid. I don't have time to develop my idea yet. I'll post when I get time, or someone else can beat me to it.
Dave Benham
It is very odd that the extra label is required. I get the wrong prompt in the output if I consolidate the thread redirection label and loop label into a single label.
I extended the method to capture the binary image of the parameter string in an ARGS variable via CERTUTIL.
Code: Select all
@echo off
REM *** Thread redirector
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F
REM *** Clear params.tmp
break > params.tmp
start "" /b cmd /k "%~d0\:GetParms:\..\%~pnx0 params.tmp"
REM *** Change prompt to achieve fixed length prefix and suffix (5 bytes each)
prompt #
REM *** Change streams permanently
REM *** stream1 and stream0 redirect to params.tmp
REM *** stream2 redirects to nul
echo on >nul 2>&1 0>&1 3>params.tmp 4>&1 5>&3
@REM *** This is the magic part, it forces a fatal syntax error, the error message itself shows the expanded %asterix without ANY modification
()%*
REM *** This is never reached, and the parent command shell terminates because stdin is invalid (an output file)
REM *** Second thread to fetch parameters as one string within args variable
:GetParms
:WaitForParmsLoop
if %~z1 EQU 0 (
goto :WaitForParmsLoop
)
setlocal enableDelayedExpansion
set "B=^!"
set "C=^"
(set L=^
%= Stores a LineFeed =%
)
for /F %%Z in ('copy /Z "%~dpf0" nul') do set "R=%%Z" %= Stores a CarriageReturn =%
REM *** Convert binary tmp file to hex
certutil -f -encodehex %1 %~n1.hex 4 >nul
REM *** Encode hex ! ^ LF CR as hex !B! !C! !L! !R! and write modified hex file
>%~n1.hex2 (
for /f "delims=" %%A in (%~n1.hex) do for %%B in (%%A) do (
set "char=%%B"
set "char=!char:21=21 42 21!"
set "char=!char:5e=21 43 21!"
set "char=!char:0a=21 4c 21!"
set "char=!char:0d=21 52 21!"
echo !char!
)
)
REM *** Convert modified hex to encoded binary
certutil -f -decodehex %~n1.hex2 %1 >nul
REM *** Read the encoded binary and decode into args variable
for /f delims^=^ eol^= %%A in (%1) do set "args=%%A"
REM *** Remove the unwanted prefix and suffix
set "args=!args:~5,-5!
REM *** Cleanup temp files
del %1 %~n1.hex %~n1.hex2
REM *** We now have the exact parameter string stored in args. Ready to begin processing
echo [!args!]
There is still one nasty potential problem with the technique - It will fail miserably if any of the streams 3 and/or 4 and/or 5 are already defined when the script is called due to prior redirection.
If I can determine the lowest 3 available streams, then the technique could be modified to always work as long as there are 3 available streams within the range 3 - 9. I think I know how to do it, but it will have undesired screen output that I don't know how to avoid. I don't have time to develop my idea yet. I'll post when I get time, or someone else can beat me to it.
Dave Benham
Re: foolproof counting of arguments
I managed to do it
The code below figures out the first 3 available unused file handles to use for the permanent redirection, and it fails cleanly if there are not 3 available handles in the range 3 - 9.
As I feared, I was not able to prevent an unwanted error message on the screen when detecting the lowest available handle.
Here are some sample results demonstrating that it works with prior redirection, unless 3 are not available
Dave Benham
The code below figures out the first 3 available unused file handles to use for the permanent redirection, and it fails cleanly if there are not 3 available handles in the range 3 - 9.
As I feared, I was not able to prevent an unwanted error message on the screen when detecting the lowest available handle.
Code: Select all
@echo off
REM *** Thread redirector
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F
REM *** Clear params.tmp
break > params.tmp
REM *** Define permanent redirection
REM *** handles 0 and 1 redirect to params.tmp
REM *** handle 2 redirects to nul
call :redirect || (
>&2 echo Unable to permanently redirect handles 0 1 and 2
exit /b 1
)
REM *** Activate the CLS below to "hide" the unwanted error message
:: CLS
REM *** Launch 2nd process to gather parameters and do main processing
start "" /b cmd /k "%~d0\:GetParms:\..\%~pnx0 params.tmp"
REM *** Change prompt to achieve fixed length prefix and suffix (5 bytes each)
prompt #
REM *** Execute the permanent redirection
echo on %redirect%
@REM *** This is the magic part, it forces a fatal syntax error, the error message itself shows the expanded %asterix without ANY modification
()%*
REM *** This is never reached, and the parent command shell terminates because stdin is invalid (an output file)
:redirect
setlocal
:: Find Highest unused handle
set "high="
call :test1 8 9 high & if defined high goto :findSecond
for %%A in (8 7 6 5) do call :test1 9 %%A high & if defined high goto :findSecond
exit /b 1
:findSecond
for /l %%A in (4 1 %high%) do call :test1 %high% %%A second & if defined second goto :findThird
:findThird
for /l %%A in (%second% 1 %high%) do call :test1 %second% %%A third & if defined third goto :findFirst
:test1
if %1 neq %2 (2>nul (%1>&%2 break))||set "%3=%2"
exit /b
:findFirst
for /l %%A in (3 1 %second%) do call :test2 %%A && (
endlocal
set "redirect=>nul 2>&1 0>&1 %%A>params.tmp %second%>&1 %third%>&%%A"
exit /b 0
)
:test2
(%second%>&%1 break)||exit /b 0
exit /b 1
REM *** Second thread to fetch parameters as one string within args variable
:GetParms
:WaitForParmsLoop
if %~z1 EQU 0 (
goto :WaitForParmsLoop
)
setlocal enableDelayedExpansion
set "B=^!"
set "C=^"
(set L=^
%= Stores a LineFeed =%
)
for /F %%Z in ('copy /Z "%~dpf0" nul') do set "R=%%Z" %= Stores a CarriageReturn =%
REM *** Convert binary tmp file to hex
certutil -f -encodehex %1 %~n1.hex 4 >nul
REM *** Encode hex ! ^ LF CR as hex !B! !C! !L! !R! and write modified hex file
>%~n1.hex2 (
for /f "delims=" %%A in (%~n1.hex) do for %%B in (%%A) do (
set "char=%%B"
set "char=!char:21=21 42 21!"
set "char=!char:5e=21 43 21!"
set "char=!char:0a=21 4c 21!"
set "char=!char:0d=21 52 21!"
echo !char!
)
)
REM *** Convert modified hex to encoded binary
certutil -f -decodehex %~n1.hex2 %1 >nul
REM *** Read the encoded binary and decode into args variable
for /f delims^=^ eol^= %%A in (%1) do set "args=%%A"
REM *** Remove the unwanted prefix and suffix
set "args=!args:~5,-5!
REM *** Cleanup temp files
del %1 %~n1.hex %~n1.hex2
REM *** We now have the exact parameter string stored in args. Ready to begin processing
echo [!args!]
output wrote: C:\test>3>nul 5>nul 7>nul 8>nul GetFullParameter Line 1^
More?
More? Line 2 ^^ ! ^& ^> ^<^
More?
More? Line 3
The handle could not be duplicated
during redirection of handle 6.
[Line 1
Line 2 ^ ! & > <
Line 3]
C:\test>3>nul 5>nul 7>nul 8>nul 9>nul GetFullParameter test
Unable to permanently redirect handles 0 1 and 2
Dave Benham
-
- Posts: 208
- Joined: 26 Dec 2013 09:28
- Contact:
Re: foolproof counting of arguments
@dbenham
I tested the last example. That's output I didn't expect to see
One more thing that seems not good. The command history is partially lost after executing the script.
I tested the last example. That's output I didn't expect to see
Code: Select all
C:\Temp>z "1 2" "3"
The handle could not be duplicated
during redirection of handle 4.
["1 2" "3]
C:\Temp>z "1 2" 3
The handle could not be duplicated
during redirection of handle 4.
["1 2]
Re: foolproof counting of arguments
I can't see any problem with deleting the :__WaitForParams label, it still works for me.
That is expected, as the main thread will be closed and only the new created "StayAlive" thread will continue.siberia-man wrote: ↑31 Aug 2018 03:01One more thing that seems not good. The command history is partially lost after executing the script.
Thats currently necessary, because the main thread has permanently changed it's stdout/stderr stream.
That's a cool technic, too.
But I would like to find a solution without the need of the "permanent redirect" technic.
It's easy to redirect the output of a fatal syntax error by redirecting the output of a call.
Code: Select all
call :fatalError > params.txt 2> nul
:fatalError
@echo on
()%*
I thought of using the "(goto) 2> nul" trick here, but then I always lose the redirection.
Code: Select all
@echo off
call :fatalError > params.txt 2> nul
:fatalError
@echo on
(
(goto)
goto :__fatal
)
:__fatal
()%*
Re: foolproof counting of arguments
You are correct
It works with a single label now. I swear I was seeing different behavior at one point, but now I can't reproduce it. When it wasn't working, the first captured prompt line had the expected "#", but the second captured prompt line had "C:\test>".
I simplified the GetParams code to use a single label instead of two.
Code: Select all
@echo off
REM *** Thread redirector
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F
REM *** Clear params.tmp
break > params.tmp
REM *** Define permanent redirection
REM *** handles 0 and 1 redirect to params.tmp
REM *** handle 2 redirects to nul
call :redirect || (
>&2 echo Unable to permanently redirect handles 0 1 and 2
exit /b 1
)
REM *** Activate the CLS below to "hide" the unwanted error message
:: CLS
REM *** Launch 2nd process to gather parameters and do main processing
start "" /b cmd /k "%~d0\:GetParms:\..\%~pnx0 params.tmp"
REM *** Change prompt to achieve fixed length prefix and suffix (5 bytes each)
prompt #
REM *** Execute the permanent redirection
echo on %redirect%
@REM *** This is the magic part, it forces a fatal syntax error, the error message itself shows the expanded %asterix without ANY modification
()%*
REM *** This is never reached, and the parent command shell terminates because stdin is invalid (an output file)
:redirect
setlocal
:: Find Highest unused handle
set "high="
call :test1 8 9 high & if defined high goto :findSecond
for %%A in (8 7 6 5) do call :test1 9 %%A high & if defined high goto :findSecond
exit /b 1
:findSecond
for /l %%A in (4 1 %high%) do call :test1 %high% %%A second & if defined second goto :findThird
:findThird
for /l %%A in (%second% 1 %high%) do call :test1 %second% %%A third & if defined third goto :findFirst
:test1
if %1 neq %2 (2>nul (%1>&%2 break))||set "%3=%2"
exit /b
:findFirst
for /l %%A in (3 1 %second%) do call :test2 %%A && (
endlocal
set "redirect=>nul 2>&1 0>&1 %%A>params.tmp %second%>&1 %third%>&%%A"
exit /b 0
)
:test2
if %second%==4 exit /b 0
(%second%>&%1 break)||exit /b 0
exit /b 1
REM *** Second thread to fetch parameters as one string within args variable
:GetParms
if %~z1 EQU 0 goto :GetParms
setlocal enableDelayedExpansion
set "B=^!"
set "C=^"
(set L=^
%= Stores a LineFeed =%
)
for /F %%Z in ('copy /Z "%~dpf0" nul') do set "R=%%Z" %= Stores a CarriageReturn =%
REM *** Convert binary tmp file to hex
certutil -f -encodehex %1 %~n1.hex 4 >nul
REM *** Encode hex ! ^ LF CR as hex !B! !C! !L! !R! and write modified hex file
>%~n1.hex2 (
for /f "delims=" %%A in (%~n1.hex) do for %%B in (%%A) do (
set "char=%%B"
set "char=!char:21=21 42 21!"
set "char=!char:5e=21 43 21!"
set "char=!char:0a=21 4c 21!"
set "char=!char:0d=21 52 21!"
echo !char!
)
)
REM *** Convert modified hex to encoded binary
certutil -f -decodehex %~n1.hex2 %1 >nul
REM *** Read the encoded binary and decode into args variable
for /f delims^=^ eol^= %%A in (%1) do set "args=%%A"
REM *** Remove the unwanted prefix and suffix
set "args=!args:~5,-5!
REM *** Cleanup temp files
del %1 %~n1.hex %~n1.hex2
REM *** We now have the exact parameter string stored in args. Ready to begin processing
echo [!args!]
I also made a simple change that eliminates the unwanted error message if both handles 3 and 4 are available. If I determine that the 2nd available handle is 4, then I know that the 1st one must be 3.
Below are some sample runs:
Note that leading and trailing spaces are stripped.Output wrote: C:\test>5>nul 6>nul 7>nul getFullParameter test
[test]
C:\test>set redirect
redirect=>nul 2>&1 0>&1 3>params.tmp 4>&1 8>&3
C:\test>4>nul 5>nul 6>nul 7>nul getFullParameter test
The handle could not be duplicated
during redirection of handle 8.
[test]
C:\test>set redirect
redirect=>nul 2>&1 0>&1 3>params.tmp 8>&1 9>&3
For example:
Code: Select all
[getFullParameter test ] yields [test].
For example:
Code: Select all
[getFullParameter= test =] yields [= test =]
But if CMD /K is used, then the full parameter on the first line is preserved. But subsequent lines are stripped, which is not surprising.
Either way, if CMD /C or CMD /K is used, then the console ends up with multiple active processes associated with it, which messes up subsequent processing within the console. So that means the script cannot be used with FOR /F or pipes.
Good luck with that. I don't see a way forward. But then again, I never expected to see the permanent redirection solution either.
Dave Benham
Re: foolproof counting of arguments
Note that my algorithm only works if you are trying to identify the first 3 available handles.
I can modify the algorithm to locate the first 2 available handles when only 2 are available, but then all tests will have undesired stderr output.
If there is only 1 available handle, then I don't know how to identify it at all.
Dave Benham
Re: foolproof counting of arguments
Ugh. I was missing a trailing quote in my last assignment. All fixed.siberia-man wrote: ↑31 Aug 2018 03:01@dbenham
I tested the last example. That's output I didn't expect to see
Code: Select all
C:\Temp>z "1 2" "3" The handle could not be duplicated during redirection of handle 4. ["1 2" "3] C:\Temp>z "1 2" 3 The handle could not be duplicated during redirection of handle 4. ["1 2]
Code: Select all
@echo off
REM *** Thread redirector
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F
REM *** Clear params.tmp
break > params.tmp
REM *** Define permanent redirection
REM *** handles 0 and 1 redirect to params.tmp
REM *** handle 2 redirects to nul
call :redirect || (
>&2 echo Unable to permanently redirect handles 0 1 and 2
exit /b 1
)
REM *** Activate the CLS below to "hide" the unwanted error message
:: CLS
REM *** Launch 2nd process to gather parameters and do main processing
start "" /b cmd /k "%~d0\:GetParms:\..\%~pnx0 params.tmp"
REM *** Change prompt to achieve fixed length prefix and suffix (5 bytes each)
prompt #
REM *** Execute the permanent redirection
echo on %redirect%
@REM *** This is the magic part, it forces a fatal syntax error, the error message itself shows the expanded %asterix without ANY modification
()%*
REM *** This is never reached, and the parent command shell terminates because stdin is invalid (an output file)
:redirect
setlocal
:: Find Highest unused handle
set "high="
call :test1 8 9 high & if defined high goto :findSecond
for %%A in (8 7 6 5) do call :test1 9 %%A high & if defined high goto :findSecond
exit /b 1
:findSecond
for /l %%A in (4 1 %high%) do call :test1 %high% %%A second & if defined second goto :findThird
:findThird
for /l %%A in (%second% 1 %high%) do call :test1 %second% %%A third & if defined third goto :findFirst
:test1
if %1 neq %2 (2>nul (%1>&%2 break))||set "%3=%2"
exit /b
:findFirst
for /l %%A in (3 1 %second%) do call :test2 %%A && (
endlocal
set "redirect=>nul 2>&1 0>&1 %%A>params.tmp %second%>&1 %third%>&%%A"
exit /b 0
)
:test2
if %second%==4 exit /b 0
(%second%>&%1 break)||exit /b 0
exit /b 1
REM *** Second thread to fetch parameters as one string within args variable
:GetParms
if %~z1 EQU 0 goto :GetParms
setlocal enableDelayedExpansion
set "B=^!"
set "C=^"
(set L=^
%= Stores a LineFeed =%
)
for /F %%Z in ('copy /Z "%~dpf0" nul') do set "R=%%Z" %= Stores a CarriageReturn =%
REM *** Convert binary tmp file to hex
certutil -f -encodehex %1 %~n1.hex 4 >nul
REM *** Encode hex ! ^ LF CR as hex !B! !C! !L! !R! and write modified hex file
>%~n1.hex2 (
for /f "delims=" %%A in (%~n1.hex) do for %%B in (%%A) do (
set "char=%%B"
set "char=!char:21=21 42 21!"
set "char=!char:5e=21 43 21!"
set "char=!char:0a=21 4c 21!"
set "char=!char:0d=21 52 21!"
echo !char!
)
)
REM *** Convert modified hex to encoded binary
certutil -f -decodehex %~n1.hex2 %1 >nul
REM *** Read the encoded binary and decode into args variable
for /f delims^=^ eol^= %%A in (%1) do set "args=%%A"
REM *** Remove the unwanted prefix and suffix
set "args=!args:~5,-5!"
REM *** Cleanup temp files
del %1 %~n1.hex %~n1.hex2
REM *** We now have the exact parameter string stored in args. Ready to begin processing
echo [!args!]
Dave Benham
Re: foolproof counting of arguments
I found a way to prevent the error message while detecting the available handles.
If we do the detection while stderr is redirected, then we just need to find the first 2 available handles. If we have 2 handles it means that there was room for the original stderr to sit on one the lower handles. We don't need to know in which handle stderr is backed up, we just need to to the permanent redirection in two steps.
For simplicity I used jeb's first proposed method for displaying the parameters, to focus more on the permanent redirection.
Code: Select all
@echo off
REM *** Thread redirector
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F
REM *** Clear params.tmp
break > params.tmp
start "" /b cmd /k @"%~d0\:StayAlive:\..\%~pnx0" "params.tmp"
prompt #
:: Detect available handles without displaying error messages
:: Continue only if there is at least 3 unused handles.
set /a "freeSlots=usedSlots=0"
2>nul (
for /L %%A in (3,1,8) do call :enumIO 9 %%A
)
:: If non of the handles 4 to 8 are free then there are three possibilities for handle 9
:: 1. It is occupied before us (Impossible to detect)
:: 2. It is occupied by redirection of stderr (Impossible to detect)
:: 3. It is free (Impossible to detect)
:: either way we don't have the required free handles
if %freeSlots% NEQ 0 (
2>nul call :enumIO 1 9
)
:: One of the handles was used by redirection of stderr
:: So we only need 2 additional free handles to continue.
if %freeSlots% LSS 2 (
echo Not enough IO Slots.
exit /b
)
:: First , do the permanent redirection of stderr. We don't have to know the backup handle.
:: The order of redirection is critical: stderr ---> freeSlots ---> usedSlots
set "stderr_permanent=break 2>nul"
for /L %%A in (%freeSlots%,-1,1) do call set "stderr_permanent=%%stderr_permanent%% %%freeIO[%%A]%%>&2"
for /L %%A in (%usedSlots%,-1,1) do call set "stderr_permanent=%%stderr_permanent%% %%usedIO[%%A]%%>&2"
%stderr_permanent%
set freeIO[
set usedIO[
set stderr_permanent
:: Next do a permanent redirection of stdout and stdin by known free handles.
echo on >params.tmp 0>nul %freeIO[1]%>&1 %freeIO[2]%>&1
()%*
:enumIO
break %1>&%2 && (
set /a "usedSlots+=1"
call set "usedIO[%%usedSlots%%]=%2"
exit /b
)
set /a "freeSlots+=1"
set "freeIO[%freeSlots%]=%2"
exit /b
REM *** Second thread to fetch and show the parameters
:StayAlive
:__WaitForParams
if %~z1 EQU 0 (
goto :__WaitForParams
)
REM *** Show the result
findstr /n "^" %1
echo,
Re: foolproof counting of arguments
The solution I posted is not complete, it does not report the used and unused handles properly if handle 9 is already in use.
I was very tired, I completely forgot to take that into account, and honestly was not able to do that, my mind was down. I should never post anything when tired.
Here is the correct (I hope) and more a organized solution.
Some test runs:
I was very tired, I completely forgot to take that into account, and honestly was not able to do that, my mind was down. I should never post anything when tired.
Here is the correct (I hope) and more a organized solution.
Code: Select all
@echo off
setlocal DisableDelayedExpansion
REM *** Thread redirector
for /F "tokens=3 delims=:" %%F in ("%~0") do goto %%F
REM *** Clear params.tmp
break > params.tmp
start "" /b cmd /k @"%~d0\:StayAlive:\..\%~pnx0" "params.tmp"
setlocal EnableDelayedExpansion
:: Detect available handles without displaying error messages
:: Continue only if there is at least 3(2) unused handles. (One is used by stderr redirection)
call :enumIO
:: One of the handles was used by redirection of stderr
:: So we only need 2 additional free handles to continue.
if %freeSlots% LSS 2 (
echo Not enough IO Slots.
exit /b
)
:: First , do the permanent redirection of stderr. We don't have to know the backup handle.
:: The order of redirection is critical: stderr ---> freeSlots ---> usedSlots
set "stderr_permanent=break 2>nul"
for /L %%A in (%freeSlots%,-1,1) do set "stderr_permanent=!stderr_permanent! !freeIO[%%A]!>&2"
for /L %%A in (%usedSlots%,-1,1) do set "stderr_permanent=!stderr_permanent! !usedIO[%%A]!>&2"
%stderr_permanent%
echo One of the 'usedIO's is occupied by redirection of stderr
echo If there is more than one, then we don't which, And no need to know
echo Only CMD knows, This is the part that enables us to prevent the error message
echo This reminds me of Quantum uncertainty principle :)
echo,
set freeIO[
set usedIO[
set stderr_permanent
:: Next do a permanent redirection of stdout and stdin by known free handles.
(
endlocal & endlocal %= To preserve prompt value after fatal error =%
prompt #
echo on >params.tmp 0>nul %freeIO[1]%>&1 %freeIO[2]%>&1
)
()%*
REM Unreachable
:enumIO
for /F "delims==" %%A in ('"(set freeIO[ & set usedIO[)2>nul"') do set "%%A=" // for displaying purposes
set /a "freeSlots=usedSlots=0"
2>nul (
for /L %%A in (3,1,8) do call :nextIO 9 %%A
)
:: If non of the handles 4 to 8 are free then there are three possibilities for handle 9
:: 1. It is occupied before us, then we MAY have one free handle which is occupied by redirection of 9
:: 2. It is occupied by redirection of stderr, then we have no free handles
:: 3. It is free, then have only one free handle which is handle 9
:: either way we don't have the required free handles (2 handles)
if %freeSlots% NEQ 0 2>nul call :nextIO 1 9
if %freeSlots% NEQ 0 2>nul (
set /a "highUsed=usedIO[%usedSlots%]"
if "!highUsed!"=="9" (
set /a "highFree=freeIO[%freeSlots%], highUsed=highFree+1"
for /F "delims==" %%A in ('"(set freeIO[ & set usedIO[)2>nul"') do set "%%A=" // for displaying purposes
set /a "freeSlots=usedSlots=0"
for /L %%A in (3,1,!highFree!) do call :nextIO !highFree! %%A
for /L %%A in (!highUsed!,1,9) do (
set /a "usedSlots+=1"
set "usedIO[!usedSlots!]=%%A"
)
)
)
exit /b
:nextIO
break %1>&%2 && (
set /a "usedSlots+=1"
set "usedIO[!usedSlots!]=%2"
(call,)
) || (
set /a "freeSlots+=1"
set "freeIO[!freeSlots!]=%2"
)
exit /b
REM *** Second thread to fetch and show the parameters
:StayAlive
:__WaitForParams
if %~z1 EQU 0 (
goto :__WaitForParams
)
REM *** Show the result
findstr /n "^" %1
echo,
Q:\test>9>nul 8>nul 7>nul 4>nul getParams test
One of the 'usedIO's is occupied by redirection of stderr
If there is more than one, then we don't know which, And no need to know
Only CMD knows, This is the part that enables us to prevent the error message
This reminds me of Quantum uncertainty principle
freeIO[1]=5
freeIO[2]=6
usedIO[1]=3
usedIO[2]=4
usedIO[3]=7
usedIO[4]=8
usedIO[5]=9
stderr_permanent=break 2>nul 6>&2 5>&2 9>&2 8>&2 7>&2 4>&2 3>&2
1:
2:#()test
3:
4:#
Q:\test>7>nul 6>nul 3>nul getParams test
freeIO[1]=5
freeIO[2]=8
freeIO[3]=9
usedIO[1]=3
usedIO[2]=4
usedIO[3]=6
usedIO[4]=7
stderr_permanent=break 2>nul 9>&2 8>&2 5>&2 7>&2 6>&2 4>&2 3>&2
1:
2:#()test
3:
4:#
Q:\test>getParams test
freeIO[1]=4
freeIO[2]=5
freeIO[3]=6
freeIO[4]=7
freeIO[5]=8
freeIO[6]=9
usedIO[1]=3
stderr_permanent=break 2>nul 9>&2 8>&2 7>&2 6>&2 5>&2 4>&2 3>&2
1:
2:#()test
3:
4:#