0

I'm trying to create a zsh alias that will run a bash environment file (called environment to set some environment variables) and then run a command foo in that environment, with the arguments that I used to invoke the alias. The problem is that sometimes I'll have an argument with spaces in them, and I haven't been able to figure out how to pass through correctly.

Here is an example of the alias, which I've tried:

foo() {
    set -x
    bash -c "source environment && foo $@"
}

When I call it like this:

foo launch --name "this is a test"

It doesn't pass through the "this is a test" argument correctly.

Here is the example output before the error:

bash -c 'source environment && foo launch' --name 'this is a test'
11
  • bash -c 'source environment && sq launch-ec2' --name 'this is a test' is putting --name into $0, and this is a test into $1... but the code source environment && sq launch-ec2 doesn't look at $0 or $1 at all, so of course they both get ignored! Commented Nov 5, 2020 at 0:45
  • BTW, if the things in the file environment should be aded to the environment, consider using the -a flag to make all subsequently-assigned variables automatically exported before sourcing it. set -a will turn that on, but you can just add it to the bash command line's flags: bash -xac ... or bash -ac ... if you don't want -x once you're done testing. Commented Nov 5, 2020 at 1:15
  • I'd also consider making it source environment && exec foo "$@" -- the exec means the copy of bash doesn't stay in memory after foo is started; and the copy of foo inherits the PID, so signals that would otherwise go to your shell instead go directly to foo. Commented Nov 5, 2020 at 1:20
  • Sorry, I accidentally copy/pasted the wrong output (from set -x) at the end which made the question unclear. I've fixed it now - it should be foo launch rather than sq launch-ec2. Commented Nov 5, 2020 at 6:19
  • I believe my answer holds, even with that clarification. Commented Nov 5, 2020 at 13:05

1 Answer 1

4

Why It Happens

When "$@" is expanded as part of a string, the preceding and following parts of that string are prepended to the first item in the "$@" list, and appended to the last item in that list.

So, when you run:

bash -c "source environment && foo $@"

...with "$@" containing the list launch --name this is a test, what you get is:

bash -c "source environment && foo launch" --name 'this is a test'

Only the launch -- the first array element -- becomes part of the bash -c argument that's parsed as source code; other members of the array then fill out $0, $1 and so forth in the copy of bash that gets invoked. And because the code source environment && foo launch doesn't look at $0, $1 or so forth, those arguments are subsequently ignored.


How To Fix It

Use single-quotes around the string you want executed as literal code. Arguments should be kept out-of-band from that code. Thus, with newlines added for (a perhaps excessive amount of) visual clarity around where each argument begins and ends:

foo() {
  set -x
  bash \
    -xc \
    'source environment && foo "$@"' \
    "$0" \
    "$@"
}

Let's break down exactly what it's passing as its argument vector, when you call foo "first argument" "second argument" in zsh with the above function definition (assuming that this is being called from a script named yourscriptname, and filling out $0 appropriately):

bash -xc 'source environment && foo "$@"' yourscriptname "first argument" "second argument"

...which, if we look at it as JSON-ish pseudocode, is an argument vector with the following contents:

[
  "bash",                             # shell to run
  "-xc",                              # -x: enable tracing; -c: next argument is code
  "source environment && foo \"$@\"", # actual code to run
  "yourscriptname",                   # new $0 value when that code is running
  "first argument",                   # $1 value when that code is running
  "second argument"                   # $2 value when that code is running
]

That's exactly what we want. bash is running code that uses "$@" to expand to the argument list it was passed itself, and that argument list is kept out-of-band from the code that's being run so you don't have injection vulnerabilities.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.