conditional piping 

Newsgroups: comp.unix.shell
> Is there a way that I control whether piping or not by a variable in
> sh script?

I thought about this problem, too, and as far as I can see there is no really elegant solution.

What I came up with now uses Bash's process substitution:

if [ ...some conditions... ]
then
   exec 3> >( tee log )  # Here goes your pipe!
else
   exec 3>&1             # To stdout otherwise.
fi
{
  date;                  # The producer: Do your stuff here.
} >&3
exec 3>-                 # Close file descriptor (and end pipe).

Martin Ramsch

conditional piping 

Yeah, Martin, you hit right on the nail top. Seems to be an elegant solution to me. :-) I'll play more with it. Thanks

conditional piping 

bash$ exec 3> >( tee log )
bash$ date>&3
Mon May 29 10:06:19 ADT 2000
bash$ exec 3>&-
bash$ cat log
Mon May 29 10:06:19 ADT 2000
exec > >( tee log | awk '{print $1}' ) ;  ls -l ; exec >&-

sh trick 

1<<, >>

sh$ dev_dest=/dev/null
sh$ echo aaa > $dev_dest
sh$ dev_dest=/dev/tty
sh$ echo aaa > $dev_dest
aaa
sh$

2<<, >>

sh$ dev_dest='> /dev/tty'
sh$ echo aaa $dev_dest
aaa > /dev/tty
sh$ eval echo aaa $dev_dest
aaa
sh$
> > What I came up with now uses Bash's process substitution:
> >
> >      exec 3> >( tee log )  # Here goes your pipe!
> > ...
> >   exec 3>&-                 # Close file descriptor (and end pipe).
>
> is there way to do the above bash trick in sh script? thanks

In plain sh the effect of process substitution can be done with explicitly created named pipes:

if [ ...some conditions... ]
then
   NPIPE=/tmp/pipe.$$  # filename for auxiliary named pipe.
   mknod $NPIPE p      # create named pipe
# Now start into the backgroud all commands
# which are to read from the named pipe:
{ sort | tee log.txt; echo "Done."; } <$NPIPE &
   exec 3>$NPIPE       # file descriptor 3 writes into the named pipe
else
   exec 3>&1           # in this case file desc 3 simply _is_ STDOUT
fi
{
   date; ...           # Do your stuff here.
} >&3                  # Every output is written to file desc #3.
exec 3>&-              # Close file desc #3.
                       # This causes EOF for the background commands.
if [ "$NPIPE" != "" ]
then
  rm $NPIPE            # Remove auxiliary named pipe again.
  unset NPIPE
fi

Martin

Since no particular problem was mentioned let me try to create one.

PROBLEM: Create a command which does the following: if $1 is '/' pipe date through tr and convert all : to / else just echo date.

The "straight forward" way of doing this is to use a script like this

if [[ $1 = "/" ]]
then
    date | tr : /
else
    date
fi

There are many alternatives

Use a variable for the command.

CMD="cat"
[[ $1 = "/" ]] && CMD="tr  :  / "
date | ${CMD}

will do the job.

Or a simpler version of this (without the cat) would be

CMD=""
[[ $1 = "/" ]] && CMD="| tr : /"
eval date $CMD

or a clearer version would be to just say

CMD="date"
[[ $1 = "/" ]]  && CMD="date | tr : /"
eval $CMD

All the above used variables that have been set up prior to the command so the conditional is not checked in the actual statement.

Try these if you want to do the checking of the conditional in the pipe

date | ( ( [[ $1 = "/" ]] && tr : / ) || cat )   # please ensure there is a space between two ((s

or more explicitly as

date |
if [[ $1 = "/" ]]
then
    tr : /
else
    cat
fi

if you dont like redundant cats ( not many do ) then try this

eval date $( [[ $1 = "/" ]] && print "| tr : /" )

It is interesting to see how much you can play around to accomplish the same thing. I am sure that the gurus here can think of more ways.

Play around :-)

Raja

1<<, >>

$ a=
$ date | ( ( [[ $a = "/" ]] && tr : / ) || cat )
Fri Mar 22 13:18:44 CST 2002
$ a=/
$ date | ( ( [[ $a = "/" ]] && tr : / ) || cat )
Fri Mar 22 13/20/02 CST 2002

2<<, >>

$ a=
$ eval date $( [[ $a = "/" ]] && printf "| tr : /" )
Fri Mar 22 13:21:00 CST 2002
$ a=/
$ eval date $( [[ $a = "/" ]] && printf "| tr : /" )
Fri Mar 22 13/21/14 CST 2002
$ eval date ` [[ $a = "/" ]] && printf "| tr : /" `
Fri Mar 22 13/21/40 CST 2002
> I tried to extend this solution for my "simple" task, which involes more
> than one piping, but failed. So the above is good for one comand piping.(?)
>  CMD="cat | tr  :  / " # test a more than one piping case
>
> and all the following failed:
>
> date | ${CMD}
> eval date | ${CMD}
> date | ( ${CMD} )

Try this as an example of a multiple pipeline command:

[[ $1 = / ]] && CMD="| tr : / | wc -l "
eval date $CMD

would succeed. This illustrates two things:

  1. eval is necessary for multiple pipeline commands to succeed. (cos you want the shell to interpret the pipe characters not just pass them of to the command as command line parameters)
  2. The command line should not contain a pipe character. For instance the second line in the above cannot be eval date | $CMD # this would fail.

If the pipeline contains nested quotes or "special" characters like (; | {}() $ []) etc then it is better to make a function out of the whole complex pipeline and call that function conditionally.

For instance in the above illustration we can rewrite it as :

function f1
{
 tr : / | wc -l
}
[[ $1 = / ]] && CMD="| f1"
eval date $CMD

multiple pipelines can also be handled by the other versions of the script that I had mentioned.

for instance it is possible to write:

eval date $( [[ $1 = / ]] && print "| tr : / | wc -l" )

Raja

>and all the following failed:
>
>date | ${CMD}
>eval date | ${CMD}
>date | ( ${CMD} )

That's because you want either:

date | eval ${CMD}

or:

eval date \| ${CMD}

The $CMD needs to be eval'd in order to get its internal pipeline interpreted correctly, and the one example you showed that has an eval statement is just doing an "eval date" and piping that to $CMD, which isn't right (though you could, if you were feeling silly:).

eval date | eval ${CMD}

Ken Pizzini