5

I'm currently experimenting with pipes. So I created a simple batch file (dos/windows) as follows:

@echo off
echo [+] starting batch file
:start
set /p msg="[+] enter msg: "
echo [+] Your message: %msg%
IF "%msg%"=="x" (
echo [x] end loop
goto exit
) ELSE (
  goto start 
)

:exit
echo [+] bye

This works fine as long as I do call it from the commandline:

> showAll.bat

S:\80_personalFolder\81_lab\python\ghoul>showAll.bat
[+] starting batch file
[+] enter msg: hello
[+] Your message: hello
[+] enter msg: x
[+] Your message: x
[x] end loop
[+] bye

S:\80_personalFolder\81_lab\python\ghoul>

But as soon as I try to pipe input to it it will run in an indefinite loop:

> echo hello | showAll.bat

will result in:

[...]
[+] enter msg: [+] Your message: hello
[+] enter msg: [+] Your message: hello
[+] enter msg: [+] Your message: hello
[+] enter msg: [+] Your message: hello
[+] enter msg: [+] Your message: hello
[...]

I dont understand that behaviour. Could someone explain what I oversee or what I'm missing! How would I fix that, so it wont run indefinitely but will stop with the next prompt within the loop, so I can exit manually?

UPDATE! First of all thanks a lot for the really good answers I received. It helped me a lot to better understand what happened. So I modified the code to reset the input var before read is called again. What I would like to achieve would be the following: 1) piping 'echo hello' to the batchscript 2) once the script ran hello from the pipe input and reenters the loop... 3) ... I would like to be able to manually input something else . instead it will still loop indefinetely with whatever default value the script will set the variable to. how can I stop the piping and switch back to the default stdin ? I guess thats the real issue I'm struggeling with. I'm not even sure this can be done.

Thanks and Best

Zapho Oxx
  • 275
  • 1
  • 16

2 Answers2

6

The set /p var= command is intended to retrieve input data to be stored into the indicated variable. The behaviour is

  • If there is data to read then the data is retrieved and stored into the indicated variable
  • If there is not data to read (no data or all data has been read), the set /p fails (with no error) and the variable is not changed.

This second case is the source of the behaviour you are seeing.

  • The "hello" (from the echo command) is read in the first iteration
  • In the second iteration there is not any data to read, but the variable is not changed.

Your code (only checking the variable contents) does not see the difference between the two cases as the variable still holds the data from the previous iteration.

If not reading any data is a valid case for loop exit, you can do something like

:start
    set "msg="
    set /p msg="[+] enter msg: "
    if not defined msg goto :exit

reseting the variable before each read and leaving if no data has been read.

This can be also written in a simpler way as

:start
    set /p msg="[+] enter msg: " || goto :exit

This uses the conditional operator || (execute next command if the previous one failed) to leave the loop if the set /p failed.

edited to adapt to comments:

The desired behaviour is to read data from the pipe present in the command, but when the data has been read, then the set /p should retrieve data from the user. First we need to see how the pipe works.

When cmd handles the pipe operator, two new separate processes are created to handle the left and right sides of the pipe.

At this moment, we have three cmd instances (the case in the OP question):

  • cmd1 : attached to the console running the typed command
  • cmd2 : running the command in the left side of the pipe
  • cmd3 : running the batch file in the right side of the pipe

The input/output streams (stdin is input stream, stdout is output stream) associated with each instance are:

  • cmd1 : stdin is current console stdin, stdout is current console stdout
  • cmd2 : inherits console stdin from cmd1, stdout is attached to the stdin stream in cmd3
  • cmd3 : stdin is attached to the stdout stream of cmd2, stdout is inherited from cmd1

In this scenario cmd3 that is handling the batch file has no way to reach the console stdin stream active in cmd1 or cmd2.

So, at this point, I don't see a way to achieve the indicated behaviour.

MC ND
  • 69,615
  • 8
  • 84
  • 126
  • Hi, first of all thanks for your detailed response. I think I understand now what is happening in my current case. However what I would like to achieve is the following (instead of exiting): once the pipe feeded hello and the loop begins from the start (after resetting the var msg, which I obviously missed in the first place) I would like the script to allow me to manually put something in instead of exiting. So first the pipe will feed hello and then repeat the loop but instead of running it indefinetely I want to be able to manually enter input. Does that make sense to you? – Zapho Oxx Jan 30 '18 at 22:34
  • @ZaphoOxx, It makes sense, but I don't see how. Answer updated with the explanation (comments are too limited). – MC ND Jan 31 '18 at 20:23
2

For your sample it's easy to fix it when you only accept one line from a pipe. I added only 'set "msg=x"' to reset the default value each time to 'x', because set /p doesn't change the variable when there isn't new input.
And set /p doesn't wait in a pipe context (only quite correct)

@echo off
echo [+] starting batch file
:start
set "msg=x"
set /p msg="[+] enter msg: "
echo [+] Your message: %msg%
IF "%msg%"=="x" (
echo [x] end loop
goto exit
) ELSE (
  goto start 
)

:exit
echo [+] bye
jeb
  • 78,592
  • 17
  • 171
  • 225
  • I don't think your code is working as desired. `echo hello | showAll.bat` will terminate after two iterations first taking "hello" as input and then "x". – MichaelS Jan 30 '18 at 16:09
  • 1
    @MichaelS I didn't say the code is fine. In my own code I would work with an empty message to stop and I would move the `IF "%msg%" ...` just below the `set /p` – jeb Jan 30 '18 at 16:12
  • OK, I guess this is the right explanation: https://stackoverflow.com/questions/3446972/why-doesnt-set-p-work-after-a-pipe – MichaelS Jan 30 '18 at 16:12
  • 1
    @MichaelS - not exactly - the setting of msg and checking its value are on the same side of the pipe. – npocmaka Jan 30 '18 at 16:25