Home > Technology > pdiff: Comparing process output

pdiff: Comparing process output

March 5, 2011

The diff command is an essential piece of any programmer’s or administrator’s toolbox, to show the difference between two files.  Or if not that tool exactly, there are many like it.  But sometimes I want to compare the output of two commands, often really the same command operating on different inputs.  You could save the output of each to separate files and diff that, or in a shell like bash you can use “<(cmd)” fed directly to diff.  For example, when I was recently dealing with an elfutils bug with prelinked binaries, I ran a command like this:

diff <(eu-readelf -hlS foo) <(eu-readelf -hlS foo.prelink)

That works just fine, though it’s a little redundant, but it gets really bad if the command gets much more complex.  And if you need to adjust the command, you have to do it in both places, or the diff falls apart.  So after getting annoyed by this, I decided to scratch that itch and write this “pdiff” script:

#!/bin/bash

CMD=
CMDARG=
DIFF=
OPTS=$(getopt -o d:gvc:I: -n pdiff -- "$@") || exit
eval set -- "$OPTS"
while true; do
    case "$1" in
        -d) DIFF="$2"; shift 2;;
        -g) DIFF=gvimdiff; shift;;
        -v) DIFF=vimdiff; shift;;
        -c) CMD="$2"; shift 2;;
        -I) CMDARG="$2"; shift 2;;
        --) shift; break;;
         *) echo "Internal error!" >&2; exit 2 ;;
    esac
done

function run() {
    local cmd
    test -n "$CMDARG" && cmd="${CMD/$CMDARG/$@}" || cmd="$CMD $@"
    echo "# $cmd"
    eval $cmd
}

case $# in
    2) exec ${DIFF:=diff -u -p} <(run $1) <(run $2);;
    3) exec ${DIFF:=diff3} <(run $1) <(run $2) <(run $3);;
    4) test -n "$DIFF" &&
       exec $DIFF <(run $1) <(run $2) <(run $3) <(run $4);;&
    *) echo "Unable to diff $# args" >&2; exit 1 ;;
esac

It even has option processing, like -v to open in Vim, -g for gVim, or -d for a custom diff command.  Then -c gives a shared command, and -I lets you specify a replacement string within the command (rather than placing arguments at the end).  Any remaining arguments get fed to separate invocations of the command for comparison.  That’s well beyond the one-liner I started with, but now I can reuse this to make future comparisons easier.  The first command I gave is now more simply:

pdiff -c 'eu-readelf -hlS' foo foo.prelink

That command can now get more complex, with compound pipelines or whatever else I need, without being such a pain to compose.

I did a little searching afterward and found that there are already other tools called pdiff, like here and here, so if I ever want to publish this more formally I should find a new name.  But for now, it meets my needs, and maybe it can be useful for someone else too.

Advertisements
%d bloggers like this: