Re: foolproof counting of arguments
Posted: 28 Aug 2018 10:27
instead of start "" / b ...
what happens if you
do it without /b?
Phil
what happens if you
do it without /b?
Phil
A Forum all about DOS Batch
https://www.dostips.com/forum/
Nice trick jeb
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
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.
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>
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!]
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
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]
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.
That's a cool technic, too.
Code: Select all
call :fatalError > params.txt 2> nul
:fatalError
@echo on
()%*
Code: Select all
@echo off
call :fatalError > params.txt 2> nul
:fatalError
@echo on
(
(goto)
goto :__fatal
)
:__fatal
()%*
You are correct
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!]
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
Code: Select all
[getFullParameter test ] yields [test].
Code: Select all
[getFullParameter= test =] yields [= test =]
Good luck with that. I don't see a way forward. But then again, I never expected to see the permanent redirection solution either.
Note that my algorithm only works if you are trying to identify the first 3 available handles.
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!]
I found a way to prevent the error message while detecting the available handles.
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,
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:#