Search code examples
bashsplitescapingwhitespacefilenames

Bash: split on space but not on escaped space


I'm trying to write a bash script that read user's input (some files so user can use TAB completion) and copy them into a specific folder.

#/bin/bash
read -e files
for file in $files
do
    echo $file
    cp "$file" folder/"$file"
done

It's ok for: file1 file2 ...

Or with : file* (even if there is a filename with space in the folder).

But it's not working for filenames with space escaped with backslash \ like : file\ with\ space escaped spaces are ignored and string is split on each spaces, even escaped.

I saw information on quoting, printf, IFS, read and while... I think it's very basic bash script but I can't find a good solution. Can you help me?


Solution

  • Clearing IFS prior to your unquoted expansion will allow globbing to proceed while preventing string-splitting:

    IFS=$' \t\n' read -e -a globs  # read glob expressions into an array
    IFS=''
    for glob in "${globs[@]}"; do  # these aren't filenames; don't claim that they are.
      files=( $glob )              # expand the glob into filenames
    
      # detect the case where no files matched by checking whether the first result exists
      # these *would* need to be quoted, but [[ ]] turns off string-splitting and globbing
      [[ -e $files || -L $files ]] || {
        printf 'ERROR: Glob expression %q did not match any files!\n' "$glob" >&2
        continue
      }
    
      printf '%q\n' "${files[@]}"  # print one line per file matching
      cp -- "${files[@]}" folder/  # copy those files to the target
    done
    

    Note that we're enforcing the default IFS=$' \t\n' during the read operation, which ensures that unquoted whitespace is treated as a separator between array elements at that stage. Later, with files=( $glob ), by contrast, we have IFS='', so whitespace no longer can break individual names apart.