Bash For Loop Spaces

How do I use bash for loop with spaces in a file name on Unix or Linux operating system? The following code fails with errors:

#!/bin/bash
files=$(ls *.txt)
dest="/nas/server/dest"
for f in $files
do
cp "$f" $dest
done

How do I fix this problem on bash shell?

[donotprint][/donotprint]For demonstration purpose create files as follows. cd to /tmp and use mkdir command to create a directory file called test:

cd /tmp/
mkdir test
cd test

Create a set of files:

echo "foo" > "This is a test.txt"
echo "bar" > "another       file     name   with  lots of   spaces   .txt"
date > "current date and time.txt"
ls -l /etc/*.conf > "My configuration files.lst"
echo "Eat in silence; work in silence." > quote.txt
echo "Eat in silence; work in silence." > quote.txt
echo "Pride is blinding" > "A Long File    Name   .      doc"

echo "foo" > "This is a test.txt"
echo "bar" > "another file name with lots of spaces .txt"
date > "current date and time.txt"
ls -l /etc/*.conf > "My configuration files.lst"
echo "Eat in silence; work in silence." > quote.txt
echo "Eat in silence; work in silence." > quote.txt
echo "Pride is blinding" > "A Long File Name . doc"

To list directory contents use ls command as follows:
$ ls -l
Sample outputs:

total 48
-rw-r--r--  1 vivek  wheel    18 Feb  4 16:01 A Long File    Name   .      doc
-rw-r--r--  1 vivek  wheel  1092 Feb  4 15:54 My configuration files.lst
-rw-r--r--  1 vivek  wheel     4 Feb  4 15:54 This is a test.txt
-rw-r--r--  1 vivek  wheel     4 Feb  4 15:54 another       file     name   with  lots of   spaces   .txt
-rw-r--r--  1 vivek  wheel    29 Feb  4 15:54 current date and time.txt
-rw-r--r--  1 vivek  wheel    33 Feb  4 15:58 quote.txt

Understanding problem

Try to read file name with spaces in a for or while loop using following syntax instead of ls command:

Syntax

for f in *
do
  echo "$f"
done

for f in *
do
echo "$f"
done

Sample outputs:

A Long File    Name   .      doc
My configuration files.lst
This is a test.txt
another       file     name   with  lots of   spaces   .txt
current date and time.txt
quote.txt

Let us try to copy files using a bash for loop to $dest directory:

#!/bin/bash
dest="/nas/path/to/dest"
################################################################
## Do not use ls command to read file names in shell for loop ##
################################################################
for f in *.txt
do
  # do something with  $f now #
  cp "$f" "$dest"
done

#!/bin/bash
dest="/nas/path/to/dest"
################################################################
## Do not use ls command to read file names in shell for loop ##
################################################################
for f in *.txt
do
# do something with $f now #
cp "$f" "$dest"
done

Wrap command line args [email protected] (positional parameters) in double quotes

You can pass command line args too. The following is a bad example:

#!/bin/bash
for f in $@
do
        echo "|$f|"
done

#!/bin/bash
for f in [email protected]
do
echo "|$f|"
done

Run it as follows:
./script *.txt
Sample outputs:

|This|
|is|
|a|
|test.txt|
|another|
|file|
|name|
|with|
|lots|
|of|
|spaces|
|.txt|
|current|
|date|
|and|
|time.txt|
|quote.txt|

The following is correct way to deal with command line args in bash for loop:

#!/bin/bash
for f in "[email protected]"
do
        echo "|$f|"
done

#!/bin/bash
for f in "[email protected]"
do
echo "|$f|"
done

Run it as follows:
./script *.txt
Sample outputs:

|This is a test.txt|
|another       file     name   with  lots of   spaces   .txt|
|current date and time.txt|
|quote.txt|

while loop with spaces example

find . | while read -r file
do
  echo "$file"
done

find . | while read -r file
do
echo "$file"
done

Or better:

find . -type f -print0 | xargs -I {} -0 echo "|{}|"

find . -type f -print0 | xargs -I {} -0 echo "|{}|"

OR

find . -type f -print0 | xargs -I {} -0 cp "{}" /path/to/dest/

find . -type f -print0 | xargs -I {} -0 cp "{}" /path/to/dest/

for loop with spaces example using IFS

Warning: Avoid using $IFS variable and is considered as a bad practice, but presented here for historical reasons. See the discussion below for more information.

O=$IFS
IFS=$(echo -en "nb")
for f in *
do
  echo "$f"
done
IFS=$O

O=$IFS
IFS=$(echo -en "nb")
for f in *
do
echo "$f"
done
IFS=$O

To process all files passed as command line args:

#!/bin/bash
O=$IFS
IFS=$(echo -en "nb")
for f in "[email protected]"
do
  echo "File: $f"
done
IFS=$O

#!/bin/bash
O=$IFS
IFS=$(echo -en "nb")
for f in "[email protected]"
do
echo "File: $f"
done
IFS=$O

Posted by: SXI ADMIN

The author is the creator of SXI LLC and a seasoned sysadmin, DevOps engineer, and a trainer for the Linux operating system/Unix shell scripting. Get the latest tutorials on SysAdmin, Linux/Unix and open source topics via RSS/XML feed or weekly email newsletter.