I've read a bunch of other questions about shell quoting and spaces in file names, and the suggestions don't seem to be working in this case.

Putting a variable with a string with an escaped space inside of another variable un-escapes the space in the final output.

This script assembles a command from user input and hard-coded values, displays the command stored in the variable, then runs it:


cpus="--cpu=host --vcpus=2"
graphics="--graphics spice"
network="--network bridge=vmbr0"
disk="--disk path=/dev/$chosen_vg_name/$lv_name"

#This line is the problem:
location="--cdrom /share/media/Linux\ ISO/debian11-server.iso";
# Run the command based on the supplied variable values:
install_cmd="virt-install -n $vm_name --memory $memory $cpus $type $variant $disk $location $graphics $network";

printf "$install_cmd\n";
exit 0;

I understand what is happening. The space in the file path in the location variable isn't getting escaped when it's added to the install_cmd variable, so the virt-install command thinks ISO/debian11-server.iso is an unknown argument.

I have also tried:

location='--cdrom /share/media/Linux ISO/debian11-server.iso';
location="--cdrom "/share/media/Linux ISO/debian11-server.iso"";
location='--cdrom /share/media/Linux\ ISO/debian11-server.iso';
location='--cdrom /share/media/Linux\\ ISO/debian11-server.iso';
location="--cdrom /share/media/Linux\\ ISO/debian11-server.iso";

What is the correct syntax to escape the space in the path name in the location variable so that it stays escaped when location is added to the variable containing the final command?


1 Answer 1




Is invoking the split+glob operator on the contents of $install_cmd and hoping the resulting words to form the expected arguments of a simple command, which hardly makes sense.

Here, you'd want either to build code in the bash language in parts that you concatenate and eventually tell the shell to interpret it with eval, which is dangerous as it can be difficult to avoid command injection vulnerabilities. Here that would have to be:

    cpus='--cpu=host --vcpus=2'
graphics='--graphics spice'
 network='--network bridge=vmbr0'
    disk='--disk "path=/dev/$chosen_vg_name/$lv_name"'
location='--cdrom "/share/media/Linux ISO/debian11-server.iso"'
install_cmd="virt-install -n $vm_name --memory $memory $cpus $type $variant $disk $location $graphics $network"
printf >&2 'Interpreting: %s\n' "$install_cmd"
eval "(PS4='Running: '; set -o xtrace; $install_cmd)"

(see how the $chosen_vg_name/$lv_name are in that case expanded upon eval).

Above, I added both some Interpreting: and Running: output to show what code the shell will interpret through eval and what resulting command is going to be run. That running will be done using some execve("/path/to/virt-install", ["virt-install", "-n"...], environ) system call, bash's xtrace output however will render it as some shell code that, if interpreted by bash would result in the same system call.

Or build the arguments of you simple command in parts using variable types that can hold several arguments: arrays and invoke the command made of the elements of that array:

    cpus=(--cpu=host --vcpus=2)
graphics=(--graphics spice)
 network=(--network bridge=vmbr0)
    disk=(--disk "path=/dev/$chosen_vg_name/$lv_name")
location=(--cdrom '/share/media/Linux ISO/debian11-server.iso')
  virt-install -n "$vm_name" --memory "$memory"
  "${cpus[@]}" "${type[@]}" "${variant[@]}" "${disk[@]}"
  "${location[@]}" "${graphics[@]}" "${network[@]}"

(PS4='Running: '; set -o xtrace; "${install_cmd[@]}")

(there, the $chosen_vg_name/$lv_name are expanded upon assignment to the $disk array).

Also remember the first argument for printf is the format. It shouldn't contain variable data.

There's there is no double interpretation of shell code, which avoids the risk of command injection vulnerabilities and is therefore preferable.

You must log in to answer this question.

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