Search code examples
bashfindwhitespacevariable-expansionifs

Bash: var expansion confusion possibly due to IFS


After reading about getting files with spaces in the name using find, I put together a small code chunk to grab all .sh files in a directory and make them executable:

find . -type f -name '*.sh' -print0 | 
while IFS= read -r -d '' file; do
  name=$(printf '%s\n' "$file" | sed "s|^\./||")
  echo $name
  if ! [[ -x $name ]] ; then
    chmod +x "${name}"
  fi
done

The third-to-last line gave me the most trouble. I went through all 3 permutations:

chmod +x $name
chmod +x ${name}
chmod +x "${name}"

I have two questions:

  1. I'd like to know why the last one, and only the last one, worked. Does it have to do with the IFS bit set earlier?

  2. If there's a simpler way to do this, what would it be? Again, it should grab all .sh files in the current directory -- all of which have spaces in their filenames -- and make them executable.


Solution

  • When you put a variable assignment at the beginning of a command, it only applies to that one command. So the IFS you set earlier was only used by the read command. The rest of the code uses the default IFS, which has whitespace as the field separator.

    If you want the IFS setting to apply to all the code, you need to do it as a separate statement, not as a prefix of a command.

    IFS=
    find . -type f -name '*.sh' -print0 | 
    while read -r -d '' file; do
      name=$(printf '%s\n' "$file" | sed "s|^\./||")
      echo $name
      if ! [[ -x $name ]] ; then
        chmod +x "${name}"
      fi
    done
    

    But in general you should quote your variables. Even if IFS is set, you will have problems if the variable contains wildcard characters, because wildcards are expanded after expanding non-quoted variables. Only leave variables unquoted if you actually need word splitting and globbing.