Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why does piping to stdin create an indefinite loop?

Tags:

batch-file

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

like image 864
Zapho Oxx Avatar asked Jan 30 '18 15:01

Zapho Oxx


2 Answers

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.

like image 164
MC ND Avatar answered Oct 02 '22 16:10

MC ND


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
like image 34
jeb Avatar answered Oct 02 '22 14:10

jeb