9

I've a linker command that looks like this:

${LD} $ld_opts $ldflags_common $ldflags "$@" -o $bin

This command is used in multiple projects of mine, and I want to postpone changing it to a free time window for chores/housekeeping.

Anyway, this trick could be of interest elsewhere, and I don't know why it's suffering from syntax errors:

LD="sh -c 'c99 \"\$@\" -lm'"

Obviously, the platform where I'm running the test, required explicit flag to link with the maths library. I'd expect this to instruct the shell to put the existing arguments in the linker command to the right of the c99 command, then add the -lm flag. However, what really emits, is:

"$@": 1: Syntax error: Unterminated quoted string

Q: How should I fix this?

11
  • I'm having trouble understanding your question - are you asking How do I add an arg to $@? Commented Apr 12 at 0:38
  • @steeldriver Yes, and I need do it to an existing command. Commented Apr 12 at 0:40
  • Your trick re-invents small wrapper scripts but in a clumsy, fragile, and error-prone way. It makes for a good example of why tricks are generally a bad idea, especially when there are well-known and non-hacky ways of doing the same thing but better. Write a wrapper script that does what you need, and set LD=/path/to/your/wrapper-script. A function may also work but may not always be in scope (i.e. they're only available in the shell they're defined in. and child shells if exported). Commented Apr 12 at 6:57
  • Also, don't use scalar variables to hold multiple args for a program, use array variables - e.g. $ld_opts should probably be an array and referenced as ${ld_opts[@]}. Commented Apr 12 at 6:58
  • 1
    yes i know. and shell quoting quirks are one of the reasons I'm saying don't use stupid tricks, do it properly. in this particular case, write a wrapper script and avoid the quoting hassles. use arrays where appropriate. Commented Apr 12 at 7:38

2 Answers 2

9

To fix LD, replace sh with perl, which allows omitting spaces:

LD='perl -e exec"c99",@ARGV,"-lm" --'

The "Unterminated quoted string" wording of that error suggests a sh implementation derived from the Almquist shell such as FreeBSD's, NetBSD's, busybox' or dash found as sh on most Debian-derived systems, while bash (still used for sh on most other GNU systems and macOS) has "unexpected EOF while looking for matching". The behavior is similar, so quoting the Bash man page:

Parameter Expansion

[...]

Quote Removal

    After the preceding expansions, all unquoted occurrences of the characters \, ', and " that did not result from one of the above expansions are removed.

Your ${LD} was "one of the above expansions", namely parameter expansion. So, when the expansion as shown by echo ${LD} produced sh -c 'c99 "$@" -lm', none of the remaining single or double quotes worked.

To illustrate this, I replaced the sh -c part, and ran the command LD="perl -E \$,=x;say@ARGV 'c99 \"\$@\" -lm'"; ${LD}, which printed 'c99x"$@"x-lm'. The remaining quotes are shown to be unprocessed, and the "x"s means the outer Bash shell was previously invoking the inner Dash shell with 5 arguments, instead of how you wanted to suppress word splitting using quotes to keep it 3 arguments.


You said you wanted to "defer changing it", so I used Perl as a workaround. It would be better to change it to an actual Bash function. So your

LD="sh -c 'c99 \"\$@\" -lm'"
${LD} $ld_opts $ldflags_common $ldflags "$@" -o $bin

should become:

my_ld() {
  c99 "$@" -lm
}
my_ld $ld_opts $ldflags_common $ldflags "$@" -o $bin
5

As an alternative to perl, you could use zsh as:

LD='zsh -c argv+=(-lm);"$@" zsh c99' your-sh-script

Same principle: when your-sh-script invokes split+glob on that unquoted ${LD} parameter expansion, with its default value of $IFS (space, tab, newline), that's split into zsh, -c, argv+=(-lm);"$@", zsh and c99, the third of which is taken by zsh as its code to interpret where we append -lm to the list of arguments, the second zsh goes into $0¹ and c99 prepended to $@ aka $argv[@].

With sh, you could use a helper environment variable:

CODE='eval c99 "$@" -lm' LD='sh -c $CODE sh' your-sh-script

This time, the code sh interprets is just $CODE on which it performs split+glob, which results in eval, c99, "$@" and -lm which runs eval with those arguments. eval joins those arguments with spaces to interpret the c99 "$@" -lm shell code² (with again sh in $0¹).


¹ not used within the inline script though the shell may use it itself inside error messages; see for instance that "$@": 1: Syntax error: Unterminated quoted string of yours, where the argument following the code argument was "$@" which made it to $0 and used as a name of that inline script used in the error message.

² And, it's rather important to understand that at that point, it's the shell syntax parser tokenisation that splits that back on those spaces and that it's unrelated to the $IFS-splitting (the split part of the split+glob operator) that is performed upon unquoted ${LD} or $CODE.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.