9

I was wondering if it is possible to read from a pipe in a batch file. If I write:

echo Test

i get, unsurprising, Test. That's nice. But what if I want to pipe the output, and read it from another command?

echo Test | echo ???

How to obtain the same result as before, but through a pipe? Thanks!

EDIT: what I am after really after is this.

I have a list of files, and i need to filter this list with some words that i put, line by line, in a file named filter.txt. So I have to use findstr /g:filter.txt.

But then I need to do something to the list files that matches, and since findstr returns one row for each file, i have to read the matches line by line.

This is how i did it:

dir /b | findstr /g:filter.txt | for /F "delims=" %a in ('more') do del "%a"

SOLUTION:

It looks like that what I wanted to do was not reading from a pipe but just reading the output of another command in a batch file.

To do a single line read, you could use this:

echo Test | ( set /p line= & call echo %%line%%)

or you can use this, that works also with multi line input:

echo Test | for /F "delims=" %a in ('more') do @echo %a

(this trick of using more could be useful in some situations). But in my particular case, the solution is this:

for /F "delims=" %a in ('echo Test') do @echo %a

Thanks to everyone!

fthiella
  • 48,073
  • 15
  • 90
  • 106

4 Answers4

8

Based on this answer https://stackoverflow.com/a/6980605/1630171 it looks like that a way to answer my question is this:

echo Test | for /F "delims=" %a in ('more') do @echo %a

It's a bit weird but it works :)

It only looks a little strange to me that there's no native solution to this... but this does exactly what i want!

Community
  • 1
  • 1
fthiella
  • 48,073
  • 15
  • 90
  • 106
  • 1
    I suggest you to always use `"delims="` FOR option instead of `"tokens=*"` to get whole lines. The second method delete leading spaces before the first token... – Aacini Nov 14 '12 at 00:25
  • The question is: What do you want to achieve? Solve a concrete problem or to get a _nicer_ solution – jeb Nov 14 '12 at 10:31
  • @jeb i'll update my question... i am not saying that this solution is not nice, in fact i belive that it is! thanks again! it's only that i feel a little surprised that there's no "native" solution to this... – fthiella Nov 14 '12 at 11:38
  • @fthiella, isn't it considered "polite" to have the user who suggested the answer to actually post it? Not 100% sure but curious... – Lizz Nov 15 '12 at 08:29
  • @lizz i don't know exactly what to do in these situations... i really hope to have been "polite", but if you think i did something wrong please tell me, i'll do my best to fix everything... i'm still learning how to correctly use SO... thanks! – fthiella Nov 15 '12 at 08:44
  • 1
    @fthiella no problem! I believe it's best to give the person who sent the answer 1-2 days to post it as "official", after which you can (if you like the answer) mark it as "Accepted". After those 1-2 days, though, in my mind, they obviously went on their merry way answering other questions. Without the 1-2 day "grace" period, though, it might look a little, um... Learning curves can be fun - hope this one's ok. :) (BTW, I've learned a LOT on SO!!) – Lizz Nov 15 '12 at 08:48
  • @lizz i answered myself (and corrected my answer with a suggestion by aacini) because Ansgar immediately pointed me to a similar answer written by jeb (+1 there)... then jeb posted another answer, +1 because it was helpful but it wasn't exactly what i was looking for, then aacini posted another solution, +1 for the explanation and i marked that as correct .. maybe next time i'll have to wait a little more before answering myself, but i really hope everything it's okay... i like this place :) thanks for your suggestion! – fthiella Nov 15 '12 at 09:41
  • @fthiella oops - my bad! Sorry, yeah, those strange twisted answers almost 100% should be answered as needed - like you did. Great job! +1 on both Q&A! – Lizz Nov 15 '12 at 09:55
7

For reading a single line, you could also use set /p, but this only works with one line.

echo test | ( set /p line= & call echo %%line%%)

The problem is here, that a pipe creates two new cmd.exe contexts, for each side one.
They run in the same window as the parent cmd.exe, they can't change any variables of the parent cmd, as they are only childs.

That's the cause why this one fails

echo test | set /p line=
echo %line%

line will be set, but it will be destroyed when the pipe ends.

jeb
  • 78,592
  • 17
  • 171
  • 225
  • thanks! this works perfectly in my example, but yes... i was looking something that works with multi lines... – fthiella Nov 14 '12 at 08:05
  • But your multiline sample is very short, I believe there isn't a shorter way. – jeb Nov 14 '12 at 10:29
  • you are right, thanks for your comment, my sample is not multiline... but i didn't know that there were two different solutions, one for single line, one for multiline... – fthiella Nov 14 '12 at 11:41
  • 1
    This worked! Which puzzles me, because... Have you ever tried running simply `Echo test | set /p line=`? It never worked for me and I can't understand why your snippet works and mine doesn't. I mean, I do `set line=`, then run that pipe, then do `echo %line%` (all these I do from command prompt), and it shows `%line%` and indeed `set l` shows no `line` variable. I wonder if you happen to know (or, at least, have any ideas) why that couldn't work. – Andriy M Nov 14 '12 at 13:37
  • Yes, I know why this works and your code not. I will edit my post – jeb Nov 14 '12 at 14:57
7

Excuse me, I think there is a confusion here...

You said you want to read from a pipe. A pipe is used to redirect the output of one command into the input of another command; the second command is called filter. For example, in

dir /b | findstr /g:filter.txt

there is a pipe between dir and findstr commands. A pipe is always established between two processes. There is no way to read the data that flow from dir command to findstr command (that is the only pipe that exist here). However, you can read from the output of findstr command.

If we insert an additional filter, the behavior is the same. For example, in

dir /b | findstr /g:filter.txt | more

there are two pipes, but there is no way to read from anyone of them. However, you can read from the output of the last command (more in this case). What is the native Batch solution to read the output of one command? It is the FOR /F command. For example, the native way to get echo command output in:

echo Test | for /F "delims=" %a in ('more') do @echo %a

is:

for /F "delims=" %a in ('echo Test') do @echo %a

Please note that in the first example the %a parameter does NOT get the information from the pipe that exist between echo and for commands, but from the output of more command.

In the same way, the natural method to achieve this task:

dir /b | findstr /g:filter.txt | for /F "delims=" %a in ('more') do del "%a"

is this way:

for /F "delims=" %a in ('dir /b ^| findstr /g:filter.txt') do del "%a"

that process the multi-line output of findstr command.

Second method is not just faster than the former, but it is also clearer because the inclusion of a more command that really do nothing may lead to undesired misconceptions or errors.

Antonio

Aacini
  • 65,180
  • 12
  • 72
  • 108
  • @aacini yes you are right... i feel a little confused :) but i this answer explains everything, the other answer was also helpful but this solution is what i was looking for! thank you very much! – fthiella Nov 15 '12 at 08:35
-1

Building on another answer I saw elsewhere, it is possible to capture the output of a command and store it in a variable without an intermediary file quite simply, so long as it is numeric.

Child processes, like those inside the pipe cannot share their environment variables, but can return a value which is picked up by %errorlevel%. %errorlevel% isn't an environment variable though, and is calculated by the shell every time it is invoked. It also cannot be set normally, and must be set using a child process. Example:

@echo off
echo %errorlevel%
cmd /c exit 56
echo %errorlevel%

Returns:

0
56

Interestingly, you can also do:

@echo off
echo %errorlevel%
cmd /c exit 56 & echo hi
echo %errorlevel%

Returns:

0
hi
56

Which I believe is because the echo hi is run by another child process in turn, which doesn't wait for the exit statement to finish before printing. This might be changed by a race condition though if the text printed is longer, I'm not to sure if the child process (running exit) which is parent to the one printing 'hi' will wait for it's child to exit (or any subsequent children either) before it completes the exit command. I tried to test this with a longer-running command like Tree, but I got a zero returned by a query to %errorlevel% which is probably due to Tree affecting the results by returning 0, possibly after the exit 56.

Anyway, to get back to what most will find useful:

@echo off
echo %errorlevel%
echo 456 | ( set /p line= & call exit %%line%% )
echo %errorlevel%
pause

Returns:

0
456

Here, the 456 printed by echo is captured and returned by subsequent queries to %errorlevel%. You can capture any command's output this way, although it's limited to one numeric value. This is still very useful, but unfortunately doesn't allow you to store textual output and I can't think of a way to make it work for multi line output either. (unexplored)

I think in theory you can chain as many commands as you want, and should be able to use && to force the order in which they run. I don't know how or if this can be used to capture multiple lines of input or allow the return of text, but should provide some additional wiggle room inside the pipe by providing nested child processes sharing their environment down and possibly return value up. (again untested, perhaps try multiple exit statements or something, and if I learn anything later I'll try to post it here)

  • 2
    It's interessting, but I can't see any relation to the question here – jeb Mar 16 '17 at 15:06
  • In conclusion: you said that `cmd /c exit 456` returns the same errorlevel than `echo 456 | ( set /p line= & call exit %%line%% )` so, why use (or even mention) a more complicated method to get the same result of a simpler one? Certainly this is _NOT_ "what most will find useful"... – Aacini Oct 23 '17 at 16:13