0

Went through this post: Pass arguments to function exactly as-is

But have a slightly different setup:

I have 3 bash functions foo, bar, baz. They are setup as follows:

foo() {
  bar $1
}

bar() {
  var1=$1
  var2=$2
  echo "$var1" test "$var2"
}
export ENV_VAR_1="1"
export ENV_VAR_2="2 3"

foo "${ENV_VAR_1} ${ENV_VAR_2}"

I'd expect the output to be:

1 test 2 3

But the output is:

1 test 2

I get why this happened. bar was executed as follows:

bar 1 2 3

My question is: how do I get it to execute

bar 1 "2 3"

Approaches I tried:

foo () {bar "$1"} 
# Out: 1 2 3 test. Makes sense since "1 2 3" is interpreted as a single argument.
1
  • The last line foo () {bar "$1"} is a syntax error. If you want a one-line function definition, you need foo() { bar "$1";} Note the space and the semicolon.
    – Wildcard
    Sep 7, 2016 at 1:07

2 Answers 2

1

This provides a single string as an argument to foo:

foo "${ENV_VAR_1} ${ENV_VAR_2}"

Because $1 is not in quotes, the shell performs word splitting and, consequently, this provides three arguments to bar:

bar $1

Word splitting is done on any IFS characters in S1. The original source of those characters is not considered.

Simpler example

Let's define x as:

$ x="${ENV_VAR_1} ${ENV_VAR_2}"

Now, let's print "$x":

$ printf "%s\n" "$x"
1 2 3

As you can see, "$x" is interpreted as one argument. By contrast, consider:

$ printf "%s\n" $x
1
2
3

In the above, word-splitting is performed on $x creating three arguments.

Shell strings have no notion of history. String x has no record of 2 3 being part of one string before x was assigned. String x just consists of 1, space, 2, space, and 3 and word splitting operates on the spaces.

Alternative: selecting your own IFS

This produces the output that you want:

$ foo() ( IFS=@;  bar $1; )
$ foo "${ENV_VAR_1}@${ENV_VAR_2}"
1 test 2 3

In foo, we set IFS to @. Consequently, all subsequent word splitting is performed using @ as the word separator. So, when calling foo, we put a @ at any location at which we want word splitting.

4
  • I understand now why it's happening. But I'm wondering if there's any way around it. Adding escaped quotes to the input sequence doesn't seem to help. Sep 6, 2016 at 3:01
  • @user2103008 I added code showing how to get the string to split where you want it to. Adding escaped quotes will not help unless eval is used and an unnecessary use of eval can lead to a variety of surprising problems.
    – John1024
    Sep 6, 2016 at 3:09
  • I agree. I ended up refactoring so that I don't have to use space separated arguments within the argument string. I'll still accept the answer you gave since it makes sense. Sep 6, 2016 at 14:40
  • 1
    Why would you stuff ${ENV_VAR_1} and ${ENV_VAR_2} together? That's the root of the problem. Keep them separate! Sep 7, 2016 at 0:53
0

You're passing a single argument to the function foo, which is the 5-character string 1 2 3. The three digits are separated by spaces. foo has no reason to treat those spaces differently.

In combining ${ENV_VAR_1} and ${ENV_VAR_2} inside a single string, you've lost information. Recovering lost information requires magic, and computers can't do magic. To retain the separation between 1 2 and 3, you need to change the way foo is called. Pass two separate arguments.

foo "${ENV_VAR_1}" "${ENV_VAR_2}"

Then all you need to do is fix the quoting in the call to bar. Either pass on the two parameters:

foo () {
  bar "$1" "$2"
}

or pass on the whole parameter list:

foo () {
  bar "$@"
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .