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.
  • 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 07 '16 at 01:07

2 Answers2

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.

John1024
  • 73,527
  • 11
  • 167
  • 163
  • 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. – user2103008 Sep 06 '16 at 03: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 06 '16 at 03: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. – user2103008 Sep 06 '16 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! – Gilles 'SO- stop being evil' Sep 07 '16 at 00: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 "$@"
}
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175