5

I am trying to get the output of a pipe into a variable. I tried the following things:

echo foo | myvar=$(</dev/stdin)
echo foo | myvar=$(cat)
echo foo | myvar=$(tee)

But $myvar is empty.

I don’t want to do:

myvar=$(echo foo)

Because I don’t want to spawn a subshell.

Any ideas?

Edit: I don’t want to spawn a subshell because the command before the pipe needs to edit global variables, which it can’t do in a subshell. Can it? The echo thing is just for simplification. It’s more like:

complex_function | myvar=$(</dev/stdin)

And I don’t get, why that doesn’t work. This works for example:

complex_function | echo $(</dev/stdin)
  • I don't understand what you're trying to do since none of your examples are correct syntax. What pipe? What is myvar supposed to contain? Could you give an example with a real command and explain what output you want to save? And what do you have against subshells anyway? – terdon Jan 17 '17 at 10:22
  • I don’t even understand why $myvar does not contain foo in my examples. After all, foo should be in stdin. I simplified the example on purpose. The echo foo thing is actually a more complicated command changing global variables, which won’t work if it’s in a subshell. – Parckwart Jan 17 '17 at 10:26
  • $(</dev/stdin) creates a subshell with empty stdin. – Ipor Sircer Jan 17 '17 at 10:27
  • Well it is, or would be if you were piping to a program that had an stdin but you seem to be attempting to pipe to a variable and that doesn't make sense. Are you just looking for myvar="foo"? if you want to assign the output of a command to a variable, then use var=$(command). There's nothing wrong with that (in fact, it is the one correct way of doing it). – terdon Jan 17 '17 at 10:27
  • 1
    Yes, you can edit variables in a subshell and no, you can't assign the output if a command to a variable without a subshell. This is what's known as an XY problem. Please edit your question and explain what you are actually trying to do. Give an example of code that reproduces your problem and we should be able to help you out. – terdon Jan 17 '17 at 10:55
6

Use command substitution:

myvar=`echo foo`

or

myvar=$(echo foo)
  • `echo foo` is just the deprecated alternative to $(echo foo), isn’t it? – Parckwart Jan 17 '17 at 10:19
  • 1
    @Parckwart yes. Well, not actually "deprecated", it's still supported. it's just that $() is better almost always. – terdon Jan 17 '17 at 10:20
  • command substitution still spawns a subshell, so it won't help the OP here. – Stéphane Chazelas Oct 23 '18 at 10:37
3

In ksh93, you can use:

var=${
  my command that updates global variable
}

That's a form of command substitution that doesn't spawn a subshell. For commands that are builtin commands, instead of having them writing their output to a pipe (for which you'd need different processes to read and write on the pipe to avoid dead-locks), ksh93 just makes them not output anything but gather what they would have been outputting in to make up the expansion.

$ ksh -c 'a=${ b=123; echo foo;}; echo "$a $b"'
foo 123

fish's command substitution also behaves like that:

$ fish -c 'set a (set b 123; echo foo); echo $a $b'
foo 123

In most other shells, you'd use a temporary file:

my command that updates global variable > file
var=$(cat file) # can be optimised to $(<file) with some shells

On Linux, and with bash or zsh (that use temp files for <<<), you can do:

{ my command that updates global variable > /dev/fd/3 &&
  var=$(cat<&3); } 3<<< ''
0

The correct solution is to use command substitution like this:

variable=$(complex_command)

as in

message=$(echo 'hello')

Your pipeline:

echo 'hello' | message=$(</dev/stdin)

or

echo 'hello' | read message

actually works. The only problem is that the shell that you're using will run the second part of the pipeline in a subshell. This subshell is destroyed when the pipeline exits, so the value of $message is not retained in shell.

Here you can see that it works:

$ echo 'hello' | { read message; echo "$message" }
hello

... but since the subshell's environment is separate (and gone):

$ echo "$message"

(no output)

One solution for you would be to switch to ksh93 which is smarter about this:

$ echo 'hello' | read message
$ echo "$message"
hello

Another solution for bash would be to set the lastpipe shell option. This would make the last part of the pipeline run in the current environment. This however does not work in interactive shells as lastpipe requires that job control is not active.

#!/bin/bash

shopt -s lastpipe
echo 'hello' | read message
echo "$message"

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.