4

How can I pass a variable that contains spaces to a script so that the script processes them correctly?

My minimal example:

test.sh

#!/bin/bash
COUNTER=1
for i in "$@"; do echo "$COUNTER '$i'"; ((COUNTER++)); done

If I call the script directly with some arguments (escaped space) it works:

./test.sh 1 2 3\ 4 5

Output (as expected):

1 '1'
2 '2'
3 '3 4'
4 '5'

If I store the arguments into a variable, the backslash is not interpreted correctly anymore as a escape char.

TEST_ARGS="1 2 3\ 4 5"
./test.sh $TEST_ARGS

Output:

1 '1'
2 '2'
3 '3\'
4 '4'
5 '5'

I would like to get the same output like before. How can I reach it?

TEST_ARGS="1 2 3\ 4 5"
# how to call ./test.sh here?

My more detailed use case is, that I have some script, which prepares all flags for my other tools / scripts.

prepare_flags.sh (very minimalistic):

#!/bin/bash
echo 'TEST_ARGS="1 2 3\ 4 5"'

In my actual script I source the output of the prepare_flags.sh script in order to access TEST_ARGS:

source <( ./prepare_flags.sh )
./test.sh $TEST_ARGS # this fails
bash -c "./test.sh $TEST_ARGS" # this works

Is there any other solution for this except the bash -c?

Totschi
  • 81
  • 2

2 Answers2

6

Use an array, not a plain variable.

(Also, there is no reason to use ancient external tools like expr with Bash; ((++COUNTER)) would work just fine. (Also, it is better to avoid uppercase variable names, because those are (by convention) used for environment variables.))

test.sh:

for ((i = 1; i <= $#; ++i)); do echo "${i} '${!i}'"; done

Passing the arguments:

test_args=(1 2 '3 4' 5)
./test.sh "${test_args[@]}"
Andrej Podzimek
  • 2,409
  • 9
  • 12
0

I just found some solution which seems to work so far:

TEST_ARGS="1 2 3\ 4 5"
bash -c "./test.sh $TEST_ARGS"

Output:

1 '1'
2 '2'
3 '3 4'
4 '5'
Totschi
  • 81
  • 2
  • 2
    Your question is great but I must warn against this answer. It's effectively the same as `eval`, which opens the door for code injection attacks. A malicious user could get your script to execute arbitrary commands such as with `TEST_ARGS='1 2 3; rm -rf /'`. It will also be difficult to put the backslashes in the right places if you're building `TEST_ARGS` at runtime. – John Kugelman Nov 10 '21 at 14:04
  • Adding another layer of expansion/indirection with `eval` or `bash -c`, such that escapes or quotes in variables will work, creates a significant code injection bug/vulnerability, because it means command list operators will also work. So arbitrary shell code in `TEST_ARGS` will be executed, as per @JohnKugelman's example. – dan Nov 10 '21 at 18:27
  • I have explained my use case a little bit more detailed in the question now. – Totschi Nov 11 '21 at 15:36