A command to output each line forward then backwards

A shell function. This will work in any shell that supports <<< here-strings, including zsh and bash.

xyz() { 
    while read line
        printf "%s " "$line"
        "$@" <<<"$line"

$ (echo "foo"; echo "bar") | xyz rev
foo oof
bar rab

There will be a lot of problems in most cases due to the way that stdio buffering works. A work around for linux might be to use the stdbuf program and run the command with coproc, so you can explicitly control the interleaving of the output.

The following assumes that the command will output one line after each line of input.

coproc stdbuf -i0 -o0 "$@"
while read -r in ; do
    printf "%s " "$in"
    printf "%s\n" "$in" >&${COPROC[1]}
    read -r out <&${COPROC[0]}
    printf "%s\n" "$out"

If a more general solution is needed as the OP only required each line of input to the program to eventually output one line rather than immediately, then a more complicated approach is needed. Create an event loop using read -t 0 to try and read from stdin and the co-process, if you have the same "line number" for both then output, otherwise store the line. To avoid using 100% of the cpu if in any round of the event loop neither was ready, then introduce a small delay before running the event loop again. There are additional complications if the process outputs partial lines, these need to be buffered.

If this more general solution is needed, I would write this using expect as it already has good support for handling pattern matching on multiple input streams. However this is not a bash/zsh solution.

In this particular case, I'd use perl:

printf '%s\n' foo bar | perl -Mopen=locale -lpe '$_ .= " " . reverse$_'
foo oof
bar rab

Which you could extend to also work with grapheme clusters:

$ printf '%s\n' $'complique\u301' |
    perl -Mopen=locale -lpe '$_ .= " " . join "", reverse/\X/g'
compliqué éuqilpmoc

Or zsh:

while IFS= read -r line; do
  print -r -- $line ${(j::)${(s::Oa)line}}

Though I would avoid using while read loops to process text, even in zsh (even if in this particular case, it's not so bad as only builtin commands are used).

In the general case, using temp files is probably the best approach. With zsh, you can do it with:

(){paste -d ' ' $1 <(rev <$1)} =(print -l foo bar)

(where =(...) takes care of creating and cleaning up the temp file).

Replacing it with pipes and some form of teeing is a recipe for deadlock in the general case. See these similar questions for some approaches and details about the deadlock situations:

  • Split an input for different command and combine the result
  • tee + cat: use an output several times and then concatenate results