Skip to content

Instantly share code, notes, and snippets.

@homeyjd
Last active December 21, 2015 13:39
Show Gist options
  • Save homeyjd/6314649 to your computer and use it in GitHub Desktop.
Save homeyjd/6314649 to your computer and use it in GitHub Desktop.
Playing with bash pipe redirection
# If you examine this command, you'll see I'm trying to run stderr through a piped command and leave stdout alone.
# The problem is that you have to redirect things around. Yes, you could simply swap stderr/out and it would work, but when they're muxed together, you get crossover.
# So, in bash world it makes perfect sense to use a new file descriptor -- #3 -- to bypass the processing in the piped commands, and then redirect 3>&1 to get it back.
# THIS WON'T WORK.
# Example from a real problem:
# This runs strace against a PHP command, removes duplicate gettimeofday system calls (which number in the 100s of thousands for running PHP processes), and colorizes the stderr output from strace as red so it can be differentiated from stdout.
$ strace -r "$@" 2>&1 1>&3 | perl -ne'$|=1; $cur = /gettimeofday/; if(!$prev){ print; }else{ if($cur){ print "."; }else{ print "\n"; print; }} $prev = $cur;' | sed $'s#.*#\e[31m&\e[m#' 1>&2 3>&1
ERROR: Bad file descriptor: 3
# The key is that there is no fd3 yet, so we create it as a shortcut to 1 with `exec 3>&1`
# THEN we can run our command as normal.
# Trivial example proving it works (should replace std with non-std ONLY for stderr content):
$ exec 3>&1; perl -e'print STDOUT "stdout\n";print STDERR "stderr\n";' 2>&1 >&3 3>&- | cat | sed 's/std/non-std/' 1>&2; exec 3>&-;
stdout
non-stderr
# Breakdown:
# Create the descriptor
exec 3>&1
# Start with a process that generates stdout and stderr content
perl -e'print STDOUT "stdout\n";print STDERR "stderr\n";'
# Push stderr into stdout, which will be piped to fd0-STDIN for the subsequent commands
2>&1
# Push stdout to fd3, which is now a shortcut to stdout
>&3
# Then we CLOSE fd3 so that it IS NOT INHERITED by the piped commands!
3>&-
# We run our piped commands against the content, which came from stderr
| cat # trivially re-echo the content
| sed 's/std/non-std/' # perform a substitution proving that the correct content came here
# ... and then we push it back to stderr where it belongs
1>&2
# Finally, we explicitly close fd3. The file descriptors belong to the shell, so the "close" operation we did above should have only affected that process invocation. The shell still owns the descriptor. So this is our "cleanup" step.
exec 3>&-
# Bringing this together, I modified my command to this, which correctly replaces and colorizes ONLY the stderr output:
exec 3>&1; strace -r "$@" 2>&1 >&3 3>&- | perl -ne'$|=1; $cur = /gettimeofday/; if(!$prev){ print; }else{ if($cur){ print "."; }else{ print "\n"; print; }} $prev = $cur;' | sed $'s#.*#\e[31m&\e[m#' 1>&2; exec 3>&-;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment