9

I need to see output on the screen and at the same time grep the output and send grep result to variable. I think it can be done with tee but I can't figure out how exactly. I tried

mycommand | tee myvar=$(grep -c keyword)
mycommand | tee  >(myvar=$(grep -c keyword))

but this does not work. How should it be, preferrably without writting to files?

Putnik
  • 2,095
  • 3
  • 23
  • 40

3 Answers3

14

You would do this:

myvar=$( mycommand | tee /dev/tty | grep -c keyword )

Use tee to pipe the output directly to your terminal, while using stdout to parse the output and save that in a variable.

glenn jackman
  • 4,320
  • 16
  • 19
  • Note: this works in `bash` but not `zsh`. Gordon Davisson's answer works for both. – DeusXMachina May 28 '21 at 14:06
  • 1
    @DeusXMachina It works in zsh for me. Can you offer a failing example? – wim Sep 30 '21 at 21:15
  • @wim Hm. Just tried it and it worked this time (zsh on mac and in a linux container). That was several months ago, no idea what I ran. Shrug. – DeusXMachina Oct 04 '21 at 16:07
  • It won't work when a TTY is not available, e.g. systemd service. [This other solution](https://serverfault.com/a/989827/954276) works even under that circumstance. – Gerard Bosch Feb 15 '22 at 16:42
10

You can do this with some file descriptor juggling:

{ myvar=$(mycommand | tee /dev/fd/3 | grep keyword); } 3>&1

Explanation: file descriptor #0 is used for standard input, #1 for standard output, and #2 for standard error; #3 is usually unused. In this command, the 3>&1 copies FD #1 (standard output) onto #3, meaning that within the { }, there are two ways to send output to the terminal (or wherever standard output is going).

The $( ) captures only FD #1, so anything sent to #3 from inside it will bypass it. Which is exactly what tee /dev/fd/3 does with its input (as well as copying it to its standard output, which is the grep command's standard input).

Essentially, FD #3 is being used to smuggle output past the $( ) capture.

Gordon Davisson
  • 11,036
  • 3
  • 27
  • 33
  • The status `$?` of this will be that of `grep`, right? – mpen Mar 31 '20 at 20:47
  • 1
    @mpen Correct; grep is the "last" command in the process substitution (the `$()` bit), so its status is reflected in the exit status of the subshell running that; that subshell is the only real command here, so its status winds up in `$?`. But note that if you did something like `{ local myvar=...` or `{ export myvar=...`, then `$?` would be the status of the `local` or `export` or whatever command instead. – Gordon Davisson Mar 31 '20 at 21:31
  • +1 as it even works with systemd services. – Gerard Bosch Feb 15 '22 at 16:43
  • When I use this in a CI pipeline, I get the error `tee: /dev/fd/3: No such device or address`. What should I do? The same happens with the `/dev/tty` solution, although I see both devices when I do a `ls -lLR /dev/`. – Joerg Sep 08 '22 at 09:24
  • @Joerg There's probably something different about the environment the CI pipeline runs in, but I have no idea what that difference is. Maybe try replacing the `tee /dev/fd/3` with `tee >(cat >&3)` (note: this must run under bash, so make sure your CI pipeline uses bash, not just sh). – Gordon Davisson Sep 08 '22 at 10:25
-2

You can use like below. If you want to append use -a option with tee remember it will create a file with your variable name.

$ ls | tee $(echo asktyagi)
asktyagi1

$ ls -lthr
total 12K
-rw-rw-r--. 1 asktyagi asktyagi    8 Oct 29 08:54 asktyagi1
-rw-rw-r--. 1 asktyagi asktyagi   23 Oct 29 08:54 asktyagi
asktyagi
  • 2,401
  • 1
  • 5
  • 19
  • Just FYI, tee will accept file name not the actual variable. if question if about that you need to find another way. – asktyagi Oct 29 '19 at 09:07
  • 1
    Perhaps there's misunderstanding. I don't need to write to file but to variable. And not to file which name is in variable. And I know tee can accept command/pipe instead of file name to send the output too. – Putnik Oct 29 '19 at 10:06
  • You should not use tee in that case `The tee utility copies standard input to standard output, making a copy in zero or more files. The output is unbuffered.` can't pass variable it should be a file. – asktyagi Oct 29 '19 at 10:31