1

I'd like to execute a statement to start a server. For that I have an environment variable to determine which server is to be started. I was given this command as a starting point:

eval "exec gunicorn --chdir /this/dir package.sub:call_main() -b 0.0.0.0:80"

As I have a few kinds of servers to start up, I would like to parameterise the script. And after searching around, I found out the quotations are redundant. So what I have now is:

APP=main
eval exec gunicorn --chdir /this/dir package.sub:call_${APP}() -b 0.0.0.0:80

This, however produces a syntax error near unexpected token '('. Ideally I would even like to have a default argument like ${APP:-main}, but I guess that is possible once the syntax error issue is resolved.

What is wrong with the statement above? Additionally, is eval or even exec needed here?

Felix
  • 123

2 Answers2

1

In your second piece of code, you have removed the double quotes around the argument to eval. Don't do that. Removing them would make () special to the shell (it starts a sub-shell).

Instead:

app=main
eval "exec gunicorn --chdir /this/dir package.sub:call_$app'()' -b 0.0.0.0:80"

The parentheses still has to be quoted here as eval re-evaluates the string. The $app variable expansion would be done before eval is called.

or,

app=main
eval "exec gunicorn --chdir /this/dir 'package.sub:call_$app()' -b 0.0.0.0:80"

which may look nicer.

Note that ${APP} and $APP are identical in every way except when immediately followed by a character that is valid in a variable name (as in "${APP}x"). Here, the {...} is not needed. Also, use lower-case variable names to avoid accidental clashes with existing environment variable.

I don't think either of eval or exec is needed here. The string does not seem to be needing re-evaluation with eval and exec would replace the current shell process with gunicorn (I don't know whether this is what you want or not).

It may be enough with

app=main
gunicorn --chdir /this/dir "package.sub:call_$app()" -b 0.0.0.0:80

Note the double-quoting.

Related:

Kusalananda
  • 333,661
  • Thank you! It seems to work without eval. And the error was indeed on the sub-shell parentheses. Reading up on exec it seems to be beneficial, as I'm using a container, so being the last command of a script it's natural to replace the shell with that process. – Felix Feb 05 '19 at 08:08
  • @Felix Yes, if the script is written to launch gunicorn, then it makes sense to use exec there. – Kusalananda Feb 05 '19 at 08:09
0

You are going in a complex task: shell are supposed to do 11 passes (and every pass mangle the input, for next step).

Your code is

APP=main
eval exec gunicorn --chdir /this/dir package.sub:call_${APP}() -b 0.0.0.0:80

To understand, remove the eval:

exec gunicorn --chdir /this/dir package.sub:call_${APP}() -b 0.0.0.0:80

Note: the first example is similar, you remove the eval and the double quotes, so you still get the same output.

Now you have the () which are reserved (which is something particular, because the token cannot be misinterpreted in other ways in such case: no prefix, we are not in context of command (so to call subshell, using parenthesis). Note: this is because of the 11 steps, and the order of tokenization. Probably reading the rules we understand why it is so. (and the shell parser is designed a lot time ago, so it is simple)

So you must tell the shell to pass () to the next parser step.

You may use escape (\):

exec gunicorn --chdir /this/dir package.sub:call_${APP}\(\) -b 0.0.0.0:80

or quotes:

exec gunicorn --chdir /this/dir package.sub:call_${APP}'()' -b 0.0.0.0:80

Note: you do no need to quote the entire "word" (paramether), you may quote just a part, so that you do no single quote the ${APP} (which need to be expanded).

For the first example, you may quote the double quotes:

eval "exec gunicorn --chdir /this/dir \"package.sub:call_main()\" -b 0.0.0.0:80"

So that eval will remove the outer quotes, and exec will see the inner quotes (unescaped)

  • @Kusalananda: I corrected it. I put the quote very too early, and so the space will be keep. Now I've corrected it (to the right position) and remover also the extra single quote (copy paste error). – Giacomo Catenazzi Feb 05 '19 at 09:04