Saturday, December 8, 2012

Handling file names with spaces in shell scripts

I decided to write this post after looking on the net for the solution to this problem. There are many recommendations but most of them are ether incorrect or involve convoluted methods like creating temporary files.
Disclaimer: I am by no means a shell scripting guru so take my advice with the grain of salt and try it for yourself. This information is relevant to bash, I do not use other shells.

The problem

You want to pass a number of files to your shell script and process each file in some way. Obvious approach is to use for loop and place processing of the file inside of the loop, something like this:

files=$*
for file in $files ; do
  process-file "$file"
done


Note the quotes around the $file variable. These are used to ensure that if the file name has some spaces, the file name is processed correctly.  Placing double quotes around the variable will ensure that spaces in it will be treated as a part of a single file name rather than multiple file names separated by spaces. If none of your file names contain spaces, the code above is perfectly workable.
The for command breaks list in the files variable using field separator which normally includes space. So in the code snipped above file will be assigned portions of the file names if the file names contains spaces. One solution recommended on the net is to modify field separator to exclude space, but this creates other problems.

The solution

The actual solution is very simple and based on often overlooked statement in the bash man page. If you omit in statement, the for command executes list once for each positional parameter that is set. So reworking above snipped as indicated below gives you the code which works correctly:


for file ; do
  process-file "$file"
done


The snipped above worked in all of my scripts. The only issue might arise when you do not pass file names on the command line. In this case function can be used or positional parameters may be reassigned using set command.