How to act on command output containing spaces in Bash loops
Bash is weird
Whenever you make an input in bash, it gets split into "words" based on whitespace. Usually, this is what you want, and it lets you write commands like
for x in ~/Documents ~/Downloads ~/Photos
do
du -h $x
done
Here, all of the filenames given are interpreted separately.
If in case you want to pass the name of a string with does have spaces in it,
you would usually quote it with single quotes, which makes sure the shell does
not process it and takes it literally.
For example 'My Photos' is the name of a single directory and not two directories.
However, the du -h $x
command will then try to access them separately and complain
about 'My' and 'Photos' not existing!
Strings with spaces in them are sometimes messed up
mkdir test
touch test/'this has spaces'
for x in $(ls test)
do
file test/$x
done
You will get the output
test/this: cannot open `test/this' (No such file or directory)
test/has: cannot open `test/has' (No such file or directory)
test/spaces: cannot open `test/spaces' (No such file or directory)
You can see that the loop tries to take each space-delimited "word" as an item of the loop, which is not what we actually want. We want to treat each line of ls as an item, and not break up each individual line.
Just to be clear, I'd personally use xargs
and do the rather silly looking
ls ~/test/ | tr "\n" "\0" | xargs -0 printf -- 'test/%s\0' | xargs -0 file
my xargs
does not seem to have the --delimiter
flag, so I had to use the
-0
flag that takes the null byte as separator.
How to control the splitting
Bash uses a special variable called $IFS
which
controls how bash splits strings.
Its default value contains the space character, tab character and the newline character.
You can edit it to only be the newline character if you wish to only split on newlines (like for example in the case of ls, which gives each file/dir its own line)
To make more resilient scripts, be sure to store the previous value of $IFS
somewhere before changing it, and revert back to that value when you're done.
This will prevent nasty and confusing bugs.
Putting the pieces together
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
I still prefer the xargs
solution.