-- Leo's gemini proxy

-- Connecting to tilde.club:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini; lang=en

Title: How to act on command output containing spaces in Bash loops

Date: 2024-02-27 14:08


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

https://en.wikipedia.org/wiki/Xargs


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


# Set IFS to newline to properly handle filenames with spaces
OLD_IFS=$IFS
IFS=$'\n'

# Loop over each file in the directory
for file in $(ls); do
    if test -f $file; then
	echo "Processing file: $file"
    fi
done

# revert to old IFS
IFS=$OLD_IFS

I still prefer the `xargs` solution.

Back to index

-- Response ended

-- Page fetched on Tue Apr 30 15:22:00 2024