2

I'm writing a shell script S which calls a program P.

P accepts its input from stdin only.

One of the parameters of my script S is optional; if the parameter is present, it denotes a file, which should be fed into P. If the parameter is missing, the stdin of S should be fed into P.

This was my attempt to solve it:

P <${3:-&0}

The idea was: If the (3rd) parameter is a filename, say: foo, it should execute

P <foo

but if it is missing or empty, it should execute

P <&0

which, to my understanding, should be equivalent to simply

P

i.e. read from stdin. This doesn't work, however. While using <&0 literally, works, as in

echo abc|cat <&0

it would produce the error message no such file or directory: &0 when used in the way I'm doing it in my script. This surprises me, since I from the zsh man page, I understand that parameter expansion occurs before setting up the redirection.

Of course I could use the trick

cat $3|P

but this doesn't look very elegant.

Could someone explain to me, why my approach did not work, and what would be a good idiom to use here?

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
user1934428
  • 19,864
  • 7
  • 42
  • 87

1 Answers1

2

Parameter expansion happens before redirection, but that doesn't help. <&0 is the <&[NUMBER] operator, whereas <${…} is the < operator followed by the word ${…} which the < operator interprets as a file name. There's no special case that would change the meaning if the file name begins with &.

You can use the exec builtin to set up a redirection and nothing else, so as to avoid duplicating code. This works well when the command is an external command.

( if [[ -n $3 ]]; then exec <$3; fi;
  exec P )

Another approach if you need P to be executed in the outer context (as opposed to a subshell) is to set up the redirection using an intermediate file descriptor.

if [[ -n $3 ]]; then exec 9<$3; else exec 9<&0; fi
P <&9
exec 9<&-

Alternatively, define a function to avoid code duplication.

P () { … }
if [[ -n $3 ]]; then P <$3; else P; fi
Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • Does it mean that this is because *tokenization* happens before parameter expansion? I had expected that ${3...} would be expanded first, so the shell then sees <&0, but if it tokenizes before, it has of course no chance to recognize <& as operator anymore :-( I really don't like the idea to explicitly use an "if" here. From the viewpoint of writing, even using "cat $3|P" would be cleaner (IMO), although it costs a pipe... – user1934428 Feb 09 '15 at 15:08
  • 1
    @user1934428 Indeed tokenisation happens before parameter expansion. It couldn't be otherwise: tokenisation has to happen for the shell to recognize parameter expansion. Similarly, `a='; cat', echo $a` prints `; cat`, it doesn't run the `cat` command. – Gilles 'SO- stop being evil' Feb 09 '15 at 15:15