Why can I not pipe input into the `read` command in Bash, in Cygwin, using ConEmu, on Windows?

0

I am trying to store output from a pipe into a variable.

After reading this post (Accessing the output of a Bash pipe with 'read'), I had some doubts remembering if I was ever successful in doing that, on a Unix system.

Then I found this post (How to loop through file names returned by find?), and was reminded that it works, sometimes using read -r <varname> too.

Environment:

User@Computer /cygdrive/...
$ echo $SHELL
/bin/bash

User@Computer /cygdrive/...
$ echo $BASH_VERSION
4.4.5(1)-release

User@Computer /cygdrive/...
$ uname -a
CYGWIN_NT-10.0 Computer 2.6.1(0.305/5/3) 2016-12-16 11:55 x86_64 

User@Computer /cygdrive/...
$ read --help
read: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
    Read a line from the standard input and split it into fields.

Attempts:

User@Computer /cygdrive/...
$ echo $var


User@Computer /cygdrive/...
$ echo "hi" | read var

User@Computer /cygdrive/...
$ echo $var


User@Computer /cygdrive/...
$ echo "hi" | read -r var

User@Computer /cygdrive/...
$ echo $var

User@Computer /cygdrive/...
$ read var
hi

User@Computer /cygdrive/...
$ echo $var
hi

User@Computer /cygdrive/...
$ echo "hi" | cat
hi

User@Computer /cygdrive/...
$ echo -e '1\n2\n3\n' | xargs
1 2 3

I also tried this in bash.exe's own process and window:

User@Computer /cygdrive/...
$ echo "hi" | read var;echo $var;

Also doing the same in the "Bash on Ubuntu on Windows" bash.exe:

echo "hi" | read var;echo $var;

Still no successful result of the assignment. I am aware I can work around this method, instead probably using $() or the redirection arrows.

Is there a way to do this using this read line command method on Windows?

Update

To some of the answers, I knew some would post alternatives like $(). This is why I intentionally mentioned this method already, including a link where several were already listed, along with specifically asking if there was a way to use the read line utility to store input into a variable, not using redirection, but instead piping data through a command chain, as I had done before on other systems, but this time on Windows.

Pysis

Posted 2017-08-02T01:14:11.203

Reputation: 980

1

this happen because read is executed in a subshell, and any variable created in that subshell is destroyed upon exit. see: http://mywiki.wooledge.org/BashFAQ/024 . the simplest workaround is to use named pipe

– Sharuzzaman Ahmat Raslan – 2017-08-02T03:05:00.393

1Your second link works only in the form something | while read var; do use $var; done because the entire while loop forms the right-end of the pipe and runs in a subshell; the setting of var is gone after the end of the while loop. @SharuzzamanAhmatRaslan: although bash 4.2+ (as in this Q) has shopt lastpipe to change this in a script. – dave_thompson_085 – 2017-08-02T05:52:58.900

Interesting: https://stackoverflow.com/questions/36340599/how-does-shopt-s-lastpipe-affect-bash-script-behavior#36340724

Probably won't use this way, as I still see several different versions of bash around, and I want to be more compatible with them.

– Pysis – 2017-08-02T18:57:05.023

Answers

0

You can use named pipe.

Example:

sharuzzaman@laptop /cygdrive/...
$ mkfifo mypipe

sharuzzaman@laptop /cygdrive/...
$ echo "hi" > mypipe &
[1] 5260

sharuzzaman@laptop /cygdrive/...
$ read asdf < mypipe

sharuzzaman@laptop /cygdrive/...
$ echo $asdf
hi
[1]+  Done                    echo "hi" > mypipe

Sharuzzaman Ahmat Raslan

Posted 2017-08-02T01:14:11.203

Reputation: 205

0

For single-line results, the canonical way to do this in bash is:

variable=$(the_command the_command_args)

for instance:

today=$(date +%Y%m%d) 

(and the command arguments can contains $-variables if necessary).

For multi-mine results you use a loop to process each line in turn:

for f in $(ls -1)
do
    echo @@@@"$f"
done

xenoid

Posted 2017-08-02T01:14:11.203

Reputation: 7 552

0

To the other answers, they seem like neat approaches with some good examples, but I was really curious why this approach that I had been using ad-hoc and in some shell scripts, 'stopped' working, or didn't work on my Windows system.

I didn't see they posted any answers directly relating to how I can accomplish this with using read and pipes, so here is my update as a form of closure where I test some examples and highlight the specific functionality that has been alluded to.

Well, to the comments on the question, I can show this works on my Mac (10.12.5) system, in iTerm 3.0.15, in the fish shell:

⋊> ~ echo "hi" | read var;echo $var;
hi
⋊> ~ fish -v
fish, version 2.6.0

When they reference a subshell being used, it seems to work in fish, which I somewhat understand has a different architecture and design pattern, although at some parts I would expect similarly functioning layers, at least from a use case perspective.

Then I tried the approach again on my Mac, in both interpreters.

In fish:

⋊> ~ echo -ne '1\n2\n3\n' | while read line; echo "a$line""a"; end
a1a
a2a
a3a

In bash:

bash-3.2$ echo -ne '1\n2\n3\n' | while read line; do echo "a$line""a"; done
a1a
a2a
a3a

bash-3.2$ echo $BASH
/bin/bash
bash-3.2$ echo $BASH_VERSION
3.2.57(1)-release

All functionality seems to be (mostly) working as expected here. I was a bit surprised to find the variable not outputting in bash, but my original problem I thought came from not getting expected data to persist to that loop. I will have to check my code more closely with all of this in mind again.

End result

The main point being highlighted here is not to store standard input into simple variables by piping in bash and continue along normally in a script, as that runs in a subshell, according to Sharuzzaman Ahmat Raslan and dave_thompson_085.

Instead, it does work that way in fish, or I can just be more careful by working directly in the pipe chain with the while loop, and go back to check my code for these problems.

Other Notes

I think another problem in one of my code lines was that I was trying to use read -r <varname>, but ended up entering read -p <varname> instead. The latter consumes an extra argument to form a prompt for the read command, and in my example with only 1 argument, leaves no useful variable name to store information into and retrieve from it. The former is sometimes useful in others' examples when not wanting to use backspace escape sequences, whenever that is. So avoid typos like this one!

Pysis

Posted 2017-08-02T01:14:11.203

Reputation: 980