ru Русский

Reticularium

NETWORKS PLACE

This is a really annoying problem we meet pretty often. You can meet it even if you don’t use Bash. For example, you upload an image named “my lovely cat.jpg” to your website and find no image there. Or it has no thumbnail. This may happen because you are using some program for making thumbnails that is being called from, say, PHP via backtick operator (that is, using unix shell) and that program interprets the file name passed to it as three different command line options: my, lovely and cat.jpg.

Well, cases like this are simple to avoid, just add double quotes, like this: yourthumbnailer "$filename", and your “my lovely bash.jpg” will be processed. Err, I meant “my lovely cat.jpg”, not going to correct it, it’s funny :)

But there may be more complex issues. And fortunately there are plenty of methods to deal with those issues.

I am far from an idea to describe all possible methods, so just a few examples, hopefully useful. Anyways, all methods are based on same ideas, and I proudly know the three of them:
  1. null character termination built into some utilities (e.g. find) to avoid special treatment of all special characters including whitespaces
  2. using double quotes where applicable
  3. using IFS variable

Here’s the examples.

Idea #1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ mkdir "test 1"
$ touch test\ 1/some\ file   # created "test 1" folder with file "some file" inside
# here is the problem:
$ find -name "test*" | xargs ls
ls: cannot access ./test: No such file or directory
ls: cannot access 1: No such file or directory
# checking if the find part works:
$ find -name "test*"
./test 1   # works perfectly
# trying the -0 option for xargs that enables null character termination:
$ find -name "test*" | xargs -0 ls
ls: cannot access ./test 1    # doesn't work, but note the difference:
: No such file or directory   # now it considers "test 1" a single folder
# what is needed is null character termination on both ends of the pipe:
$ find -name "test*" -print0 | xargs -0 ls
some file   # works perfectly now
# using find's built-in -exec feature is often preferrable:
$ find -name "test*" -exec ls '{}' \;
some file   # works just fine without null character termination

Idea #2

1
2
3
4
5
6
7
$ for dir in test*; do echo $dir; ls $dir; done
test 1    # note that echo $dir works, so it's assigned correctly
ls: cannot access test: No such file or directory   # ls $dir doesn't work
ls: cannot access 1: No such file or directory      # double quotes are needed
$ for dir in test*; do echo "$dir"; ls "$dir"; done
test 1
some file

Idea #3

IFS is a system variable, it stands for “Internal Field Separator”. “I Feel Stupid” is a good match, too, because the idea to use IFS often comes after a long time spent for escaping and quoting and many lines of unneeded code written. By default, IFS is set to a tab, newline and space bound together so any of these works as a field separator.

1
2
3
$ (IFS='';for dir in test*; do echo $dir; ls $dir; done) # parenthesis needed
test 1                                           # to run this in a subshell leaving
some file                                        # the original IFS untouched

More complex example:

1
2
3
4
5
6
7
8
9
10
$ mypath="test 1/test 2/test 3"  # some recursive folders, each needs to
$ for folder in $mypath; do echo $folder; done         # be processed
test                                           # I am simulating processing
1/test                                       # these folders with the echo
2/test                                       # command. Apparently the result
3                                              # is not what we wanted
$ IFS1=$IFS; IFS='/'; for folder in $mypath; do echo $folder; done; IFS=$IFS1
test 1
test 2
test 3

I used IFS reassigning here instead of subshell, this way you can use it in scripts reassigning at the beginning and assigning back at the end or for some part of code where needed.

Comment it: