Search code examples
bashvariablestarquotation-marks

bash - zip multiple files, take argument from variable, one of them has space in name


I want to zip file.txt and file with spaces.txt. I need to keep them in one variable. I can concat these string like this:

files="$files \"$newfilename\""

Then I have all the file names in one variable, separated by space and each one covered with quotation marks. "file.txt" "file with spaces.txt"

so I need to zip them now. However If I do:

tar czf output $files

then bash will produce:

tar czf output '"file.txt."' '"file' with 'spaces.txt"'

If I do

tar czf output "$files"

then bash will do:

tar czf output '"file.txt." "file with spaces.txt"'

In first case, bash insert a apostrophe after and before each word, in the second case, tar takes both files as one name. What should I do to produce tar czf "file.txt" "file with spaces.txt" if I have exactly this string in $files variable?


Solution

  • Using a variable for storing multi word entries that are independent. Use an array and quote the file-names properly, so the names with spaces are preserved

    declare -a files=()
    files=("file.txt")
    files+=("file with spaces.txt")
    

    The +=() is used to append elements to an existing array. Now expanding the array is what you need to pass the list to zip

    tar czf output "${files[@]}"
    

    Regarding OP's question on the context between doing files=() and declare -a files=(). They are probably the same thing and work in the same context of initializing indexed arrays. But a noticeable difference happens when you do declare -a files without the () part. Because declare -a does not re-initialize an array which is already defined but =() empties it. Refer to the example below

    prompt> files=()
    prompt> files+=(a "new word")
    prompt> files+=("another word")
    prompt> echo "${files[@]}"
    a new word another word
    

    Now doing files=() would empty the existing the array completely,

    prompt> files=()                   # array completely emptied now
    prompt> echo "${files[@]}"
                                       # empty result
    

    But with the same content as previously and doing

    prompt> echo "${files[@]}"
    a new word another word
    prompt> declare -a files           # an existing array is not emptied
    prompt> echo "${files[@]}"
    a new word another word