3

This script was posted as answer to a Question. And I'm trying to work out what's going on.

result=$(
  {
    {
      ssh host app-status >&3 3>&-; echo "$?"
    } | {
      until read -t1 ret; do
        printf . >&2
      done
      exit "$ret"
    }
  } 3>&1
)

Here's my explanation, but I'm sure it's wrong.

  1. The outer {...} 3>&1 : fd 3 is opened as a duplicate of stdout
  2. Then second half of pipe, '| { }` no redirection
  3. In read loop printf's stdout is duplicated with stderr, so in 2. (above) all output actually comes via stderr.
  4. Now the first half of pipe, { ssh ... } | : stdout of ssh is dupped onto fd3 (which is in fact stdout from 1. fd 3 is closed with 3>&- so stdout is re-opened onto what it was originally and this is piped into 2.
  5. finally, echo just prints to stdout

So my question (problem with understanding) is; Is the result of this the same as just printf to stderr ? What does the voodo redirect to 3 actually achieve? Is there anything asynchronous in here ?

X Tian
  • 10,463

3 Answers3

4

Inside $(...), stdout (fd 1) is a pipe. At the other end of the pipe, the shell reads the output and stores it into the $result variable.

With: $({ blah; } 3>&1) we make it that both fd 3 and 1 point to that pipe in blah.

blah is cmd1 | cmd2. There cmd1 and cmd2 are started concurrently with cmd1 fd 1 pointing to another pipe (the one to cmd2), however we don't want ssh output to go to that pipe, we want it to go to the first pipe so that it can be stored in $result.

So we have { ssh >&3; echo "$?"; } | cmd2, so that only the echo output goes to the pipe to cmd2. ssh output (fd 1) goes to $result. ssh not needing a fd 3, we close it for it after we've used it to set fd 1 (3>&-).

cmd2's input (fd 0) is the second pipe. Nothing is writing to that pipe (since ssh is writing to the first pipe) until ssh terminates and echo outputs the exit status there.

So in cmd2 (being the until loop), the read -t1 is actually waiting with timeout until ssh exits. After which, read will return successfully with the content of $? fed by echo.

  • Argh! I see, ssh does go to original stdput and produces result which is what you want, and nothing is sent through pipe by ssh, only the echo $? of ssh. :-) very clever ! I like the read loop timeout too. tks – X Tian Feb 06 '14 at 12:56
1

Ok, firstly what happens if you execute a remote command via ssh

ssh  otherhost /bin/true; echo $?
0

and

ssh otherhost /bin/false; echo $?
1

so echo "$?" prints the return value of app-status to stdout (file handle 1)

  1. The outer {...} 3>&1 redirects all input from file handle 3 to stdout
  2. >&3 3>&- redirects stdout to file handle 3 and closes file handle 3.
  3. read -t1 ret reads input from stdin to variable ret
  4. printf . >&2 prints . to stderr (file handle 2)

Good read Advanced Bash Scripting Guide

0

The basic idea behind the voodo redirections of fd 3 to fd 1 and fd 1 to fd 3 in the group command { ... 1>&3 3>&- ...} 3>&- 3>&1 is to make the output of the ssh command bypass the ssh | until pipe via fd 3.

Anything written to fd 3 inside the group command with 1>&3 will get around the pipe mechanism.

This way the read command (which will "return failure if a complete line of input is not read within TIMEOUT seconds", see help read in bash) will only get to read the exit status of the ssh command and the output assigned to the result variable can be restricted to the output of the ssh command.

# examples for bypassing a pipe using output redirections

# cf. http://unix.stackexchange.com/a/18711
echo hello | sed 's/hello/HELLO/'
{ echo hello 1>&3 3>&- | sed 's/hello/HELLO/'; } 3>&- 3>&1

{
result=$(
  {
    {
      ssh localhost 'sleep 5; echo "ssh output"; exit 55' >&3 3>&-; echo "$?"
    } | {
      until read -t1 ret; do
        printf . >&2
      done
      printf '\n%s\n' "exit $ret" >&2
      exit "$ret"
    }
  } 3>&1
)
echo "result: $result"
}

# output:
# .....
# exit 55
# result: ssh output

And, last but not least, duplicating a stdout stream is done by man 1 tee, not by output redirections. The outer {...} 3>&1 redirection means that fd 3 is opened and points to where fd 1 is pointing to, i. e. to stdout.

Further reading:

nuro
  • 1