Variable containing multiple args with quotes in Bash

I generate a bash variable containing all my args and those args contain spaces. When I launch a command with those args - eg. ls $args - quotes are not correctly interpreted. Here is an example - also creating and erasing needed files.

#!/bin/bash
f1="file n1"
f2="file n2"
# create files
touch "$f1" "$f2"
# concatenate arguments
args="\"$f1\" \"$f2\""
# Print arguments, then launch 'ls' command
echo "arguments :" $args
ls $args
# delete files
rm "$f1" "$f2"

With that, I have some "no such file" errors for "file, n1", "file and n2"


You might consider using an array for the args, something like this:

args=( "$f1" "$f2" )
ls "${args[@]}"

(The problem you're hitting at the moment is that once interpolation has happened there's no difference between intra- and inter- filename spaces.)


eval will first evaluate any expansions and quoting and then execute the resultant string as if it had been typed into the shell.

f1="file n1"
f2="file n2"
args="'${f1//\'/}' '${f2//\'/}'"
# will execute `ls 'file n1' 'file n2'`
eval "ls $args"

The above example uses input sanitisation to prevent arbitrary commands being executed (f1="'; rm -rf '/").

When using eval it is important to consider the source of the data being eval'd. If eval is being used for command compositing (building a command dynamically), with all values static, and defined by your script, there are likely no additional considerations. If your script is a persistent process, running as root, reading values from a world writable pipe, it's probably a good idea to ensure user input is always treated as literal data.

The input sanitisation above relies on the fact that single quoted strings are treated as literals, that is, they are not expanded, and escape sequences are not processed. To prevent arbitrary command execution we need to prevent the single quoted string being terminated, this can be done by removing any single quotes from the input.

If you're sure you don't need command sanitisation then the string substitutions can be omitted:

f1="file n1"
f2="file n2"
args="'${f1}' '${f2}'"
# will execute `ls 'file n1' 'file n2'`
eval "ls $args"

EDIT: This answer was rewritten to place an example with the least potential for harm, first. Thanks to @CharlesDuffy for the reminder that examples should be secure by default.


Use set to set your variables as positional parameters; then quoting will be preserved if you refer to them via "$@" or "$1", "$2", etc. Make sure to use double quotes around your variable names.

set -- "$f1" "$f2"
touch "$@"
ls "$@"
rm "$@"