How to detect if input comes from pipe or redirected file

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

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

How to detect if input comes from pipe or redirected file

#1 Post by jeb » 25 Oct 2011 07:40

Hi,

this is an interessting code, as it fits to some other problems (like set /p, and FOR/F file reading)

You can detect how your batch is called

Code: Select all

type file.txt | myBatch.bat

or

Code: Select all

< file.txt myBatch.bat

or

Code: Select all

myBatch.bat


From Frank P. Westlake alt.msdos.batch.nt

Code: Select all

:: inputDemo2.cmd
:: From the desk of Frank P. Westlake, 2011-09-21
::
:: Determines if there is redirected or piped input waiting and identifies
:: which it is. This test conforms to the precedence of REDIRECTION over PIPE.
::
:: This test is not reliable because if file handle 3 is mishandled by any
:: process in this console group then the handle can become lost or confused.
::
:: This test is not reliable because there are other command line
:: configurations which will pass the test as a PIPE but which have no pipe.
::
:: Unfortunately this test prints a blank line in the console window
:: if input is not REDIRECTION.
@Echo OFF
SetLocal EnableExtensions

:: This file catches the error report if input is REDIRECTION. It will be deleted.
Set "TempFile=%TEMP%\inputTest"

:: INPUT either remains undefined or it is defined with either "REDIRECTION" or "PIPE".
Set "INPUT="

:: BEGIN TEST.
:: Print something redirected to the console window. This redirection fails if the
:: input stream has been redirected from a file; an error report is sent to "%TempFile%".
:: ERRORLEVEL does not receive the value of this error so the report is necessary.
:: This redirection does not fail if it is done in FOR's execution chamber so it must be
:: done here.
Echo(>&3 2>"%TempFile%"

For %%f in ("%TempFile%") Do (
  ERASE "%TempFile%"
  REM If file size is not 0 there was a redirection error so the input type is REDIRECTION.
  If "%%~zf" NEQ "0" (
    Set "INPUT=REDIRECTION"
  ) Else (
    REM "I'm_HERE"'s pipe test. Determine if a PIPE is indicated.
    Echo %CMDCMDLINE%|FINDSTR "\"/c\" %0">NUL: && Set "INPUT=PIPE"
  )
)
:: Finished. Show the result.
If DEFINED INPUT (
  Echo Input type is %INPUT%
  REM Show the input.
  MORE
) Else (
  Echo No PIPE and no REDIRECTION.
)


jeb

Sponge Belly
Posts: 216
Joined: 01 Oct 2012 13:32
Location: Ireland
Contact:

Re: How to detect if input comes from pipe or redirected fil

#2 Post by Sponge Belly » 13 May 2013 10:00

Hello Jeb!

Only found this earlier topic today. :oops:

But my question still stands: I’m looking for a robust method to check for redirected input (ie, prog.cmd < file.txt).

The program in the OP correctly identifies redirected input from a file. But like FPW says in the comments, it’s fragile, requires a temporary file, and outputs a blank line to the console. Is there a more robust solution? Can the above code be improved upon?

And I’m aware of Aacini’s typeofhandle program, but it’s a .com file and they won’t work on 64-bit Windows. Anyways, my point is I’m looking for something as portable as possible. Any helpful suggestions gratefully appreciated. :-)

- SB

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

Re: How to detect if input comes from pipe or redirected file

#3 Post by dbenham » 17 Sep 2018 09:08

The technique in the original post is very fragile, to the point I would never rely on it. The redirected input test only works if there has not been any other redirection prior to redirecting the input.

For example, the following falsely claims there is no redirected input or pipe:

Code: Select all

>nul <file.txt MyBatch.bat

Dave Benham

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

Re: How to detect if input comes from pipe or redirected file

#4 Post by siberia-man » 17 Sep 2018 09:35

To detect quickly if STDIN of a batch script is bound (piped or redirected, it doesn't matter) I use the command similar the following one:

Code: Select all

timeout /t 0 2>nul
if errorlevel 1 rem do something when STDIN bound
Unfortunately it works for STDIN only.

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

Re: How to detect if input comes from pipe or redirected file

#5 Post by dbenham » 17 Sep 2018 12:05

Great idea :!: :idea: 8)

I forgot about that TIMEOUT feature. I was always irritated by that design, but I never thought to apply the feature in this case. Very useful.

TIMEOUT is not available to XP, but hopefully that should not be much of an issue. At long last I think that Win version is finally a dying breed in the wild.


Dave Benham

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

Re: How to detect if input comes from pipe or redirected file

#6 Post by jeb » 17 Sep 2018 23:27

@siberia-man
It's cool and simple :D

Yesterday, I build a new solution to detect STDOUT redirection.
Currently, the detection for STDERR redirection is not working correct.
[strike]It produces a wrong result when the STDOUT redirection is active.[/strike]
Edit: I found a simple way for STDERR, 1>&2 needs to be present in the findstr statement.

The complete concepts is build on the tabulator/backspace technic of @Aacini Move cursor to *any position* using just ECHO command.
The trick is, that trying to move the cursor before the home position results into an error message, but there is no such message when the STDOUT/ERR is redirected

Code: Select all

@echo off
setlocal
call :createTAB_BS
call :isSTDOUTRedirected stdoutRedir
call :isSTDERRRedirected stderrRedir
(
echo(
echo %1 %2
echo STDOUT = %stdoutRedir%
echo STDERR = %stderrRedir%
) > CON
pause > nul
exit /b

:isSTDOUTRedirected
cls
call :__detectRedirection %1
exit /b


:isSTDERRRedirected
cls 1>&2
call :__detectRedirection %1 "1>&2"
exit /b

:__detectRedirection
setlocal DisableDelayedExpansion
( ^(echo %%TAB%%%%BS%%%%BS%%^) ) | ( findstr "^"  %~2 2> nul)
set /a __tmp=!%errorlevel%
(
endlocal
set "%1=%__tmp%"
)
exit /b

:createTAB_BS
for /f "tokens=1,2 delims=," %%A in ('forfiles /p "%~dp0." /m "%~nx0" /c "cmd /c echo(0x09,0x08"') do (
    set "TAB=%%A"
    set "BS=%%B"
)
exit /b

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

Re: How to detect if input comes from pipe or redirected file

#7 Post by siberia-man » 18 Sep 2018 02:08

@jeb

I am not sure that I totally understand what the last script does:

z ZERO NIL

Code: Select all

ZERO NIL
STDOUT = 1
STDERR = 1
z ONE UNO | z TWO DUO > nul

Code: Select all

TWO DUO
STDOUT = 1
STDERR = 1


ONE UNO
STDOUT = 1
STDERR = 1

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

Re: How to detect if input comes from pipe or redirected file

#8 Post by jeb » 18 Sep 2018 02:52

I used an extra test script, my main script has the name detectRedirect.bat

Code: Select all

@echo off
call detectRedirect.bat #1 stdout_default

call detectRedirect.bat #2 stdout2Nul > nul

call detectRedirect.bat #3 stderr2Nul 2> nul

call detectRedirect.bat #4 both2Nul 1> nul 2> nul
Output wrote: #1 stdout_default
STDOUT = 0
STDERR = 0

#2 stdout2Nul
STDOUT = 1
STDERR = 0

#3 stderr2Nul
STDOUT = 0
STDERR = 1

#4 both2Nul
STDOUT = 1
STDERR = 1
siberia-man wrote: z ZERO NIL

Code: Select all

ZERO NIL
STDOUT = 1
STDERR = 1
@siberia-man
That is unexpected, I got for the same test with
detectRedirect.bat ZERO NIL
Output Win7x64 wrote:ZERO NIL
STDOUT = 0
STDERR = 0

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

Re: How to detect if input comes from pipe or redirected file

#9 Post by dbenham » 18 Sep 2018 08:50

First off, I don't like any method that reads from stdin or writes to stdout or stderr because I view that as destructive - the result of a script can change from what it was without any detection.

But ignoring that concern...

"#1 No redirection", "#3 stderr redirected", and "#4 both redirected" all work for me.

But "#2 stdout redirected" does not work. The control codes, including the CLS formfeed, are all non-functional, causing a little box to be printed to the console for each control character. And the script lists both stdout and stderr as redirected. I've used * to represent each little box on my output screen

Code: Select all

*******#2 stdout2Nul**STDOUT = 1**STDERR = 1**
I'm testing on Win 10. (both legacy console, as well as new console)

I've added a couple more tests:

Code: Select all

call detectRedirect.bat #5 stdout2stderr 1>&2

call detectRedirect.bat #6 stderr2stdout 2>&1
--OUTPUT--

Code: Select all

#5 stdout2stderr
STDOUT = 0
STDERR = 0

#6 stderr2stdout
STDOUT = 0
STDERR = 0
I suppose those are the expected results, but I don't think that is what we want.

Lastly I did a couple crazy tests redirecting to stdin :twisted:

Code: Select all

call detectRedirect.bat #7 stdout2stderr 1>&0
--OUTPUT--

Code: Select all

The system cannot write to the specified device.

#7 stdout2stdin
STDOUT = 0
STDERR = 1
That certainly is wrong.

But the real kicker is when I redirect stderr to stdin:

Code: Select all

call detectRedirect.bat #8 stderr2stdin 2>&0
This crashes and kills the console :!:


Dave Benham

sst
Posts: 93
Joined: 12 Apr 2018 23:45

Re: How to detect if input comes from pipe or redirected file

#10 Post by sst » 18 Sep 2018 20:28

dbenham wrote:
18 Sep 2018 08:50
But the real kicker is when I redirect stderr to stdin:

Code: Select all

call detectRedirect.bat #8 stderr2stdin 2>&0
This crashes and kills the console :!:
cmd will not be crashed by this, rather it will terminate itself in a controlled manner.

At first I thought stack overflow might be the cause of the termination, as it may repeatedly try to write to stderr but that will generate another error and this continues until the stack is full. Then I checked the behavior in debugger, and it turned out that cmd explicitly checks if it can write to stderr, and if it can not, it terminates itself immediately by calling CRT exit function.

From the batch programming perspective it doesn't make much difference how cmd is terminated, but it guarantees that the behavior is by design.

This can be used for example to detect if the right side of the pipe is terminated. As I used the technique in this SO Answer

Code: Select all

(
    for /L %%# in (0,0,1) do @(
        echo doIt
        timeout /t 1 /nobreak >nul
        %= This guarantees we will not loop infinitely when the right side of pipe is closed =%
        echo PingPipe 2>&1
    )
) | MyCustomStreamConsumerApp

Post Reply