Search code examples
pipekshstderr

How to pipe stderr while leaving stdout alone in ksh?


I'm using pax to backup a directory over a ssh link. If like to have a progress report saying how much of the transfer has occurred. With the -v option, it'll output the filename that it's done to stderr, so I figured I could get the count of how many files are in the directory and somehow pipe stderr into my shell script and report how far it's gotten.

Thing is I'm not sure how to pipe stderr without disrupting stdout. It's running ksh on the remote machine.

So this is what I have so far which will be run on the remote machine:

file_count=$(find "$target" -type f | wc -l)
count=0
progress() {
  while [ $((++count)) -lt $file_count ]; do
    echo -n "$((count * 100 / file_count))%\r" 1>&2
    read
  done
  echo 100% 1>&2
}

# This line needs to be modified somehow so that
# stderr goes to progress while leaving stdout
# alone.
echo "$target" | pax -r -v | progress 

Seems that even though read says it supports -n, it doesn't, so I can't read from a particular handle.


Solution

  • The general way to do this sort of thing in a Bourne shell is to temporarily redirect stdout to an auxiliary file descriptor. Something like:

    { cmd 2>&1 1>&3 | progress; } 3>&1
    

    Here, the stdout of cmd goes to the original stdout, while the stderr of cmd goes to progress. In your case, you should write:

    { echo "$target" | pax -r -v 2>&1 1>&3 | progress; } 3>&1 
    

    Note that any errors from echo will not go into the pipe to progress (which is probably fine, and probably what you want), and you may need to be concerned with buffering issues. Also note the double quotes on $target. For robustness, you should probably use printf '%s\n' "$target" rather than echo.