31

Say I want to echo something and capture it in a variable, at the same time I see it in my screen.

echo "hello" | tee tmp_file
var=$(< tmp_file)

So now I could see hello in my terminal as well as saving it into the variable $var.

However, is there any way to do this without having to use a temporary file? tee doesn't seem to be the solution, since it says (from man tee) read from standard input and write to standard output and files, whereas here it is two times standard output.

I am in Bash 4.3, if this matters.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
  • Related: [Can the output of one command be piped to two other commands?](https://superuser.com/questions/7448/can-the-output-of-one-command-be-piped-to-two-other-commands) – Gabriel Staples Apr 15 '20 at 08:57
  • @xhienne Actually this question is clearer, that one has nice answers but there's some useless noise too. – oguz ismail Feb 15 '21 at 14:22
  • @oguzismail No, those answers are misleading at best. One should not write to `/dev/tty` when one actually wants to write to `stdout`. `/dev/tty` may not exist at all. – xhienne Feb 15 '21 at 14:25
  • @xhienne I see. [This one](https://stackoverflow.com/a/49959484/10248678) doesn't write to `/dev/tty`. And similarly, process substitution may not be supported at all. – oguz ismail Feb 15 '21 at 14:29
  • @oguzismail That's exactly why the other answer is better. Here you have to dig until the least-upvoted answer to find something correct. There, the accepted answer is correct, and if your bash is too old to offer command substitution then the second and third answers will do the trick. – xhienne Feb 15 '21 at 14:34
  • @xhienne you can always suggest improvements to the answers or explain them in comments. Posts are not written on stone and the idea of the site lies on keeping them improved. Also OPs sometimes read comments :P and may change the acceptance mark. – fedorqui Feb 15 '21 at 14:44
  • If it were up to me I would edit the accepted answer to include other alternatives here, and close the other one as a duplicate of this. But no one likes that when I do so :/ – oguz ismail Feb 15 '21 at 14:47
  • @oguzismail I think it is a very useful approach that everyone will benefit from. I would gladly accept it if it was one of my posts. – fedorqui Feb 15 '21 at 14:51
  • @fedorqui'SOstopharming' IMO, the best answer is not here, but the second one of the other questions. Why would I duplicate the answer here? Quoting the help center: "The fundamental goal of closing duplicate questions is to help people find the right answer by getting all of those answers in one place." Are you questioning this? – xhienne Feb 15 '21 at 14:53
  • @xhienne all I am saying is the fundamentals of the site: improve posts so they are useful to next people seeing them. I am not expert enough on /dev/tty to know if [what you say](https://stackoverflow.com/questions/37067895/#comment117055922_37067895) applies to all cases. For this, I think it is good to have an updated answer explaining its drawbacks and alternative options. Such approach is only present [here](https://stackoverflow.com/a/53338288/1983854). I don't mind if the duplicate is this or the other one, I share your goal of having good content together. – fedorqui Feb 15 '21 at 15:12
  • @fedorqui'SOstopharming' You are perfectly right on the fundamental goals of this site. And what helps improving answers is to gather them in one place. Your example is good and illustrates again why this question should be closed as a dupe, because triplee insightful comment is only there, not here. Regarding what I say, anyone that has done `ssh server command` knows that sometimes there is no tty. Besides, `/dev/stdout != /dev/stderr != /dev/tty` is sh 101. – xhienne Feb 15 '21 at 15:29

4 Answers4

45

Use tee to direct it straight to screen instead of stdout

$ var=$(echo hi | tee /dev/tty)
hi
$ echo $var
hi
123
  • 10,778
  • 2
  • 22
  • 45
10

Pipe tee does the trick.

This is my approach addressed in this question.

var=$(echo "hello" | tee /dev/tty)

Then you can use $var to get back the stored variable.

For example:

var=$(echo "hello" | tee /dev/tty); echo "$var world"

Will output:

hello
hello world

You can do more with pipes, for example I want to print a phrase in the terminal, and at the same time tell how many "l"s are there in it:

count=$(echo "hello world" | tee /dev/tty | grep -o "l" | wc -l); echo "$count"

This will print:

hello world
3
Community
  • 1
  • 1
Leo
  • 13,428
  • 5
  • 43
  • 61
  • How is this different from my answer ? – 123 May 06 '16 at 13:32
  • @123 As of this particular question, no. However, I have another requirement which is a little bit more complicated than this one, in the question I mentioned above. And I figured it out by myself. So the OP invited me to post here as well. That's it. – Leo May 06 '16 at 13:40
  • Thanks for posting the answer as well! It was funny how fast everything happened: you replying in the other question, me asking here and @123 posting his answer. Now we have all the info in the same place, easier to find. Thanks to all! – fedorqui May 09 '16 at 08:31
6

A variation on Ignacio's answer:

$ exec 9>&1                                                                                                              
$ var=$(echo "hello" | tee >(cat - >&9))   
hello                                                                              
$ echo $var
hello

Details here: https://stackoverflow.com/a/12451419/1054322

MatrixManAtYrService
  • 8,023
  • 1
  • 50
  • 61
  • Very nice, since it doesn't depend on specific filenames on the proc filesystem (i.e. also works on MacOS). `exec 10>&1` fails on zsh (and closes the shell, because it interprets `10` as the command to replace the shell with (and then fails because `10` is not a valid command) but replace 10 with 9, and everything works :) : `exec 9>&1` – Claude Dec 25 '18 at 17:19
5

Send it to stderr.

var="$(echo "hello" | tee /dev/stderr)"

Or copy stdout to a higher FD and send it there.

$ exec 10>&1
$ var="$(echo "hello" | tee /proc/self/fd/10)"
hello
$ echo "$var"
hello
fedorqui
  • 275,237
  • 103
  • 548
  • 598
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • Uhms, very interesting. Could you elaborate the FD part a little? `exec 10>&1` sounds strange to me. – fedorqui May 06 '16 at 08:44
  • 2
    @fedorqui: `exec` serves two purposes in bash. The first is to replace the current process with a new process. The second is to operate on file descriptors using redirection syntax. If no command is specified then the FD redirections are applied to the current process. – Ignacio Vazquez-Abrams May 06 '16 at 08:46
  • 1
    @fedorqui `&1` points to `/proc/self/fd/1` which points to `/dev/pts/1` which is the terminal screen. This point `/proc/self/fd/10` to the same file as `/proc/self/fd/1` i.e the terminal screen. This means that when it is tee it goes straight to the terminal, whereas stdout(&1) is picked up by the assignment. It is a roundabout way of just teeing to `/dev/pts/1`. – 123 May 06 '16 at 08:50