Detect redirection and EOF in Standard Handles

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
Aacini
Expert
Posts: 1910
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Detect redirection and EOF in Standard Handles

#1 Post by Aacini » 15 Jan 2012 23:02

At this post I saw this strange example posted by jeb:

jeb wrote:

Code: Select all

@echo off
setlocal EnableDelayedExpansion
< testfile.txt  (
   set /p line=
   echo set/p-read: !line!

   echo --- FINDSTR ---
   findstr /n ^^

   echo --- MORE ---
   more

   echo --- FIND ---
   find /n /v ""
)
...
None of the commands close the input stream, even if they are at the end the stream is still open.

FIND and MORE first resets the file position variable and then read the complete file to the EOF, so both can be used to reread the same file multiple times.
jeb

Previous code open my eyes to a so obvious concept I didn't realized before: in any Batch program, subroutine or parentheses-enclosed code block, the DOS standard file handles remains open until the program/subroutine/block ends. Remember that every DOS-Windows running program have 5 Standard Handles identified by an integer number this way: 0=STDIN (keyboard CON, redirected by <), 1=STDOUT (screen CON, redirected by >), 2=STDERR (screen CON, redirected by 2>), 3=STDAUX (serial port AUX) and 4=STDPRN (printer PRN).

EDIT: Windows documentation indicate that handles 3-9 are undefined (available), although it seems that CMD.EXE connect handle 3 to CON: device (keyboard for input, screen for output).

I made a small test to confirm that the standard handles behaves as expected:
thefile.txt wrote:Line one
Line two
Line three
Line four
Line five
The program:

Code: Select all

@echo off
setlocal EnableDelayedExpansion
findstr /N ^^ thefile.txt | find /C ":" > lines.tmp
set /P lines=< lines.tmp
rem Copy twice the number of input lines:
set /A lines*=2
< thefile.txt (
   for /L %%i in (1,1,%lines%) do (
      set /P line=
      echo Copy of line %%i: !line!
   )
) >> thefile.txt
And the result:
Result wrote:Line one
Line two
Line three
Line four
Line five
Copy of line 1: Line one
Copy of line 2: Line two
Copy of line 3: Line three
Copy of line 4: Line four
Copy of line 5: Line five
Copy of line 6: Copy of line 1: Line one
Copy of line 7: Copy of line 2: Line two
Copy of line 8: Copy of line 3: Line three
Copy of line 9: Copy of line 4: Line four
Copy of line 10: Copy of line 5: Line five
The new data appended to the end of the file via STDOUT handle is ready to be read when STDIN's File Pointer reaches it, although both handles refer to the same physical file.

With this idea as base, I wrote a very small and simple .COM program that allows to detect if a standard handle has been redirected to a disk file.

Code: Select all

TYPEOFHANDLE handle

Return the type of given handle via ERRORLEVEL this way:

IF %ERRORLEVEL% LSS 128 (
Handle connected to character device: 3=CON (4=NUL in original DOS)
) ELSE (
Handle connected to disk file: +128=disk file, +64=EOF (low part=drive in original DOS)
)

EDIT: If %errorlevel% is zero, the handle is not connected to any file (undefined handle, like handles 3-9 in Windows CMD.EXE).

For example:

Code: Select all

TypeOfHandle 0
if %errorlevel% geq 128 (
    rem STDIN redirected to disk file
    echo This program must be executed in interactive (not predefined-input) way^!
    goto :EOF
)
set /P input=Enter input please:
etc...
Another one:

Code: Select all

TypeOfHandle 1
if %errorlevel% == 3 (
    echo Summary of results to be displayed in the screen...
) else (
    echo The complete results
    echo that will be stored
    echo in the redirected disk file...
)
This program also provide (at least!) a method to detect EOF in redirected input file:

Code: Select all

:nextLine
   set line=
   set /P line=
   echo(%line%
   TypeOfHandle 0
   set /A eof=%errorlevel% ^& 64
if %eof% == 0 goto nextLine
To get TYPEOFHANDLE.COM program, insert this line at beginning of the Batch file:

Code: Select all

if not exist TypeOfHandle.com call :CreateTypeOfHandle
... and this subroutine at end:

Code: Select all

:CreateTypeOfHandle
setlocal DisableDelayedExpansion
set TypeOfHandle=ëWëOë82Ò2ÿ³‹û2íŠMÿã^>° üó®t7Š]ÿ€û0r/€û9w*€ë02À´DÍ!rÎö€tË€âëĀʀ€â¿°',!´DÍ!"Àu²€Ê@ŠÂ´LÍ!ë«
setlocal EnableDelayedExpansion
echo !TypeOfHandle!> TypeOfHandle.com
exit /B


P.S. - When both STDIN and STDOUT handles are redirected to disk files, this command: SET /P VAR=%VAR% do a simultaneous output-input operation: the previous value of VAR is sent to STDOUT (with no CR+LF) and a new value of VAR is read from STDIN.

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

Re: Detect redirection and EOF in Standard Handles

#2 Post by siberia-man » 08 Nov 2014 10:40

Seems there is no internal way to detect if a handler is redirected to a file or another pipe.

With this idea as base, I wrote a very small and simple .COM program that allows to detect if a standard handle has been redirected to a disk file


Aacini, what if you share the original assembler code of the program? Trivial copy-and-paste method doesn't allow to believe that the stored file if the same one as you have.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Detect redirection and EOF in Standard Handles

#3 Post by dbenham » 08 Nov 2014 15:43

I think Aacini already knows this, but the following information is still not correct.
Aacini wrote:Remember that every DOS-Windows running program have 5 Standard Handles identified by an integer number this way: 0=STDIN (keyboard CON, redirected by <), 1=STDOUT (screen CON, redirected by >), 2=STDERR (screen CON, redirected by 2>), 3=STDAUX (serial port AUX) and 4=STDPRN (printer PRN).

EDIT: Windows documentation indicate that handles 3-9 are undefined (available), although it seems that CMD.EXE connect handle 3 to CON: device (keyboard for input, screen for output).

The above edited statement was largely due to the following behavior:

Code: Select all

C:\>echo Hello 1>&3
Hello
The output is written to the console

But there is newer information available at http://stackoverflow.com/q/9878007/1012053, and viewtopic.php?p=14612#p14612

The MicroSoft docs are correct. Only handles 0, 1 and 2 are predefined. Handles 3 and above are undefined.

It is easy to show that 4 is undefined

Code: Select all

C:\>echo Hello 1>&4
The handle could not be duplicated
during redirection of handle 1.

Handle 3 seems to point to the console, but it only seems so because the redirection temporarily stores the original definition of 1 in the first available undefined handle, which normally is 3. So it is effectively redirecting 1 to itself!

This behavior is further demonstrated below:

Code: Select all

C:\>echo Hello 3>nul 1>&4
Hello
Earlier we saw that 4 was undefined, but now it is pointing to the console :!: This is because 3 is already taken, so the first available undefined handle is 4, so that is where the original definition of 1 is temporarily stored. When the redirection of 1 is finished, it has effectively redirected to itself.


Dave Benham

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

Re: Detect redirection and EOF in Standard Handles

#4 Post by Aacini » 09 Nov 2014 15:56

Ok. Finally I updated this program to WIN-32 API, so it is now an .exe file that run in all Windows versions. I spent several hours looking for the corresponding documentation because it is bad named/located as a File Windows function, although it is really a Handle one! :shock: After the documentation was found, it took me less than 10 minutes to write the new program. :evil:

The Batch file below create the new GetFileType.exe auxiliary program and describe how to use it.

Code: Select all

@echo off
setlocal EnableDelayedExpansion

rem GetFileTypeDoc.bat: Create GetFileType.exe auxiliary program and describe it
rem Antonio Perez Ayala

if not exist GetFileType.exe (
   echo Extracting GetFileType.exe
   call :ExtractBinaryFile GetFileType.exe
   echo GetFileType.exe file created
   echo/
)

echo Get the file type of the given handle
echo/
echo    GetFileType.exe handle
echo/
echo The result is reported via ERRORLEVEL this way:
echo/
echo -2      No handle given or bad handle number.
echo -1      The handle number is not connected to any file.
echo  0      The type of the specified file is unknown.
echo  1      The specified file is a disk file.
echo  2      The specified file is a character file, typically a device or console.
echo  3      The specified file is a socket, a named pipe, or an anonymous pipe.
echo/
echo/
echo Some examples:
echo/
echo C:\^> GetFileType.exe 0
echo C:\^> echo %%errorlevel%%
echo 2
echo/
echo C:\^> GetFileType.exe 0 ^< file.txt
echo C:\^> echo %%errorlevel%%
echo 1
echo/
echo C:\^> type file.txt ^| GetFileType.exe 0
echo C:\^> echo %%errorlevel%%
echo 3
echo/
echo C:\^> GetFileType.exe 3
echo C:\^> echo %%errorlevel%%
echo -1
echo/
goto :EOF


rem Extract Binary File from hexadecimal digits placed in a "resource" in this .bat file

:ExtractBinaryFile filename.ext[.cab]
setlocal EnableDelayedExpansion
set "start="
set "end="
for /F "tokens=1,3 delims=:=>" %%a in ('findstr /N /B "</*resource" "%~F0"') do (
   if not defined start (
      if "%%~b" equ "%~1" set start=%%a
   ) else if not defined end set end=%%a
)
(for /F "skip=%start% tokens=1* delims=:" %%a in ('findstr /N "^" "%~F0"') do (
   if "%%a" == "%end%" goto decodeHexFile
   echo %%b
)) > "%~1.hex"
:decodeHexFile

rem Modified code based on :genchr subroutine
type nul > t.tmp
(for /F "usebackq" %%a in ("%~1.hex") do (
   set input=%%a
   set i=0
   for /L %%I in (0,2,120) do for %%i in (!i!) do if "!input:~%%i,1!" neq "" (
      set hex=!input:~%%i,2!
      set /A i+=2
      if "!hex:~0,1!" neq "[" (
         set /A chr=0x!hex!
         if not exist !chr!.chr call :genchr !chr!
         type !chr!.chr
      ) else (
         for /L %%J in (1,1,5) do for %%i in (!i!) do if "!input:~%%i,1!" neq "]" (
            set "hex=!hex!!input:~%%i,1!"
            set /A i+=1
         )
         if not exist 0.chr call :genchr 0
         for /L %%J in (1,1,!hex:~1!) do type 0.chr
         set /A i+=1
      )
   )
)) > "%~1"
del *.chr
del t.tmp temp.tmp
del "%~1.hex"

rem Expand created file if extension is .cab
set "filename=%~1"
if /I "%filename:~-4%" equ ".cab" (
   expand "%filename%" "%filename:~0,-4%" > NUL
   del "%filename%"
)

exit /B


:genchr
REM This code creates one single byte. Parameter: int
REM Teamwork of carlos, penpen, aGerman, dbenham
REM Tested under Win2000, XP, Win7, Win8
set "options=/d compress=off /d reserveperdatablocksize=26"
if %~1 neq 26 (
   makecab %options% /d reserveperfoldersize=%~1 t.tmp %~1.chr > nul
   type %~1.chr | ( (for /l %%N in (1,1,38) do pause)>nul & findstr "^" > temp.tmp )
   >nul copy /y temp.tmp /a %~1.chr /b
) else (
   copy /y nul + nul /a 26.chr /a >nul
)
exit /B


<resource id="GetFileType.exe">
4d5a900003[3]04[3]ffff[2]b8[7]40[35]b0[3]0e1fba0e00b409cd21b8014ccd21546869732070726f6772616d2063616e6e6f74206265207275
6e20696e20444f53206d6f64652e0d0d0a24[7]551e49c1117f2792117f2792117f27929f603492167f2792ed5f3592137f279252696368117f2792
[8]5045[2]4c01020066de5f54[8]e0000f010b01050c0002[3]02[7]10[3]10[3]20[4]40[2]10[3]02[2]04[7]04[8]30[3]02[6]03[5]10[2]10
[4]10[2]10[6]10[11]1420[2]28[84]20[2]14[27]2e74657874[3]86[4]10[3]02[3]02[14]20[2]602e7264617461[2]9c[4]20[3]02[3]04[14]
40[2]40[8]e83b[3]e85a[3]33c048488a1e80fb30721f80fb39771a80e30fb8f6ffffff2ac350e850[3]0bc07c0650e840[3]50e834[3]cccccccccccc
e83b[3]8bf08a06463c2275098a06463c2275f9eb0c8a06463c20740484c075f54ec38a06463c2074f94ec3ccff250c204000ff2500204000ff250420
4000ff25082040[379]5e20[2]6c20[2]7c20[2]5020[6]3c20[10]8e20[3]20[22]5e20[2]6c20[2]7c20[2]5020[6]9b004578697450726f63657373
001f0147657446696c6554797065006a0147657453746448616e646c65[2]e600476574436f6d6d616e644c696e6541006b65726e656c33322e646c6c
[358]
</resource>

Please, report any problem you may have with this program...

Antonio

Post Reply