kiwisan
kiwisan

Reputation: 489

bash Command Substitution with Spaces Issue

I'm trying to populate some exclusions to run a backup script on the command line by reading the values from an external file and prefixing these with -x. There will be a variable number of exclusions, including none at all. The trouble I'm running into is that when bash expands this variable, it frames it with single quotes. This confuses the backup script.

#cat sss2.sh:

#!/bin/bash
IFS=$'\n'
xcl=""
for x in $(cat /root/exfile)
 do
 xcl="$xcl -x $x"
 done
backupbin /sourcepath /destination "$xcl"

# cat exfile
"*.tgz"
"*.zip"

When I run this, bash expands the variable $xcl with single quotes, which prevents backupbin from running. It is due to the spaces, because if I didn't have the -x, the single quotes disappear.

# /bin/bash -x ./sss2
+ IFS=''
+ xcl=
++ cat exfile
+ for x in '$(cat /root/exfile)'
+ xcl=' -x "*.tgz"'
+ for x in '$(cat exfile)'
+ backupbin /sourcepath /destination ' -x "*.tgz" -x "*.zip"'

I have tried this as expanding a populated array, and have tried different combinations of single quotes, double quotes, and back ticks. I've tried using eval without success:

backupbin /sourcepath /destination $(eval echo "$xcl")

Finally, I've tried creating an array and expanding this, but I get the same result:

IFS=$'\n' 
excludefile=($(cat /root/exfile))
if [ ${#excludefile[@]} -eq 0 ]; then
 echo ""
 else
 for i in "${excludefile[@]}"
 do
 printf "%s" " -x $i"
 done

What am I doing wrong?

Upvotes: 2

Views: 1308

Answers (2)

rici
rici

Reputation: 241731

As always with this sort of problem, the simplest solution to this kind of problem is to use an array, rather than to try to juggle quotes. (Quote+eval solutions almost always fail in some corner case, sometimes disastrously.)

#!/bin/bash
IFS=$'\n'

xcl=()
for x in $(< /root/exfile); do
  xcl+=(-x "$x")
done

backupbin /sourcepath /destination "${xcl[@]}"

See the bash FAQ.

Edit: as @konsolebox notes, the use of $(</root/exfile) is subject to pathname expansion. So if the file contained a line like "foo*" and one or more files whose names start with foo exist in the current working directory, then the exclusion pattern -- which was probably intended to be a wildcard exclusion -- will instead by replaced by the list of filenames. It would better to use:

#!/bin/bash
xcl=()
while IFS= read -r x; do xcl+=(-x "$x"); done < /root/exfile
backupbin /sourcepath /destination "${xcl[@]}"

Upvotes: 5

konsolebox
konsolebox

Reputation: 75488

@rici's suggestion about using an array is already correct. As for reading lines, better use this form:

while read -r x; do
    xcl+=(-x "$x")
done < /root/exfile

Reading values through word splitting is not recommended.

Upvotes: 2

Related Questions