2

When I run the following script, output of ls -la is stored to variable $output.

#! /bin/sh

output=$(ls -la)

How can I pipe output of ls -la to stdout and $output?

I am asking in the context of running borgbackup which can output for a long time during backups.

I would like to be able to track progress when I manually run script while storing output in $output to send it to sysadmin via email.

sunknudsen
  • 6,356
  • 3
  • 39
  • 76
  • 3
    Explore `tee(1)`. –  Feb 15 '21 at 13:58
  • 2
    You may already know about the pitfalls of parsing the output of `ls`. But since future readers of this question may not, I still feel compelled to post this reading material: https://mywiki.wooledge.org/ParsingLs – kojiro Feb 15 '21 at 14:01
  • 1
    You see the two answers give you an answer. I was trying to teach you something. –  Feb 15 '21 at 14:01
  • Thanks @Roadowl! I was aware of `tee`, but didn't know I could use `/dev/tty`. Thanks for your contribution to teaching me more about `tee`. ☺️ – sunknudsen Feb 15 '21 at 14:04
  • 4
    Do not conflate stdout with the tty. Use tee to write to `/dev/stdout`, not to `/dev/tty` – William Pursell Feb 15 '21 at 14:07
  • 3
    But note that you need to write to `/dev/stdout` of the main shell, not the stdout of the subshell. eg `exec 3> /dev/stdout; output=$(ls -la | tee /dev/fd/3)` – William Pursell Feb 15 '21 at 14:13
  • @WilliamPursell Agreed, this should rather be a dupe of [this question](https://stackoverflow.com/q/12451278/7233423). The answers of the current dupe mention `tee /dev/tty` which is misleading. – xhienne Feb 15 '21 at 14:21

2 Answers2

3

Use tee;

output=$(ls -lta | tee /dev/tty)

Another way of doing this is by creating a copy of STDOUT, and again using tee to send the output there;

# Create copy of stdout
exec 3>&1

# Run command
output=$(ls -lta | tee /dev/fd/3)

# Close copy
exec 3>&-
0stone0
  • 34,288
  • 4
  • 39
  • 64
  • Interesting, thanks! Are there advantages to using the `/dev/fd` approach over `tee`? – sunknudsen Feb 15 '21 at 14:07
  • Not quite sure, maybe this could be useful when calling multiple commands. I guess it depends on your actual situation! – 0stone0 Feb 15 '21 at 14:08
  • It's more readable to use `/dev/tty` or `/dev/stdout` if all you need is to print to stdout. – Chen A. Feb 15 '21 at 14:12
  • OP asks to output to `stdout`, not to `/dev/tty` which is not the same, and which may even not exist. The `exec` part is correct. – xhienne Feb 15 '21 at 14:57
  • @0stone0 I am trying to learn more about `exec 3>&1` but can't find intelligible docs (I am probably missing some fundamentals). Does writing to `exec 3>&1` write to disk in any way or is this memory-only? – sunknudsen Feb 17 '21 at 12:47
  • @0stone0 When using the file descriptor approach on Debian, I get `tee: /dev/fd/3: Permission denied`. Would you happen to know what I am doing wrong? – sunknudsen Feb 17 '21 at 13:42
1

Use tee util and pass it to /dev/tty to print stdout.

box: ~/demo
➜ out=$(ls -la | tee /dev/tty)
total 16
drwxr-xr-x    4 chen  staff   128 Feb 15 15:59 .
drwxr-xr-x+ 103 chen  staff  3296 Feb 15 15:59 ..
-rw-r--r--    1 chen  staff   141 Sep  1 12:38 docker-compose.yml
-rw-r--r--    1 chen  staff    84 Sep  1 12:31 ubuntu.Dockerfile
box: ~/demo
➜ echo $out
total 16
drwxr-xr-x    4 chen  staff   128 Feb 15 15:59 .
drwxr-xr-x+ 103 chen  staff  3296 Feb 15 15:59 ..
-rw-r--r--    1 chen  staff   141 Sep  1 12:38 docker-compose.yml
-rw-r--r--    1 chen  staff    84 Sep  1 12:31 ubuntu.Dockerfile
Chen A.
  • 10,140
  • 3
  • 42
  • 61
  • OP asks to output to `stdout`, not to `/dev/tty` which is not the same, and which may event not exist. – xhienne Feb 15 '21 at 14:58
  • @xhienne `/dev/tty` works for both Linux and Mac. – Chen A. Feb 15 '21 at 15:09
  • What do you mean by "works"? First, I'm saying this is not the same (you have to know that `/dev/stdout != /dev/stderr != /dev/tty`). Second this question is not tagged Linux or Mac. Third, I should have rather said that programs are not always connected to a terminal, even in Linux or Mac. – xhienne Feb 15 '21 at 15:34
  • Well.. you're right, except that by default, every `sh` process is connected to these streams. So, using the OP script above, on both Mac and Linux works as is, without any additional special care. So it answers the OP question. – Chen A. Feb 15 '21 at 15:55
  • By default every `sh` process is connected to stdout and stderr, right, but there is absolutely no guarantee for `/dev/tty` (e.g. a Cron job is not connected to a terminal). But you got me wrong: I'm saying that you should not assume stdout = tty (even if a tty exists, stdout can be a redirection to something else). stdout is stdout, nothing else. – xhienne Feb 15 '21 at 16:07