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

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.

3

4 Answers

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

TL:DR - In bash only the very first command in a sequence of pipes executes in the current shell.

In some other shells, eg ksh, the last command will execute in the current shell.

Since read is an shell built-in command it allows you to set viariables in the active environment, eg the current shell.

For example

read value
echo $value

But when the read is at the end of a sequence of pipes, bash will run the first command in the current shell and all the other commands, including the read, in other shells.

echo 10 | read value

Here the value var is not passed up to the current shell (exported variables can be passed down to children only)

The solution is to not use pipes.

read value <<<$(echo 10)

This works because read, the first command, will run in the current shell.

Another solution is to use a temporary file

command1 | command2 | command3 > /tmp/tempfile
read value < /tmp/tempfile

Special note:

Assignments with value substitution can be used if you assign only a single variable, eg:

datetoday=$(date)

(Or my pet-hate doing the same using backticks - datetoday=`date`.)

But that is not an option when assigning multiple variables, eg. to assign values to 3 variables at the same time:

read year month day <<<$(date '+%Y %m %d')
echo Year: $year
echo Month: $month
echo Day: $day

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

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!

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like