BASH for loop works nicely under UNIX / Linux / Windows and OS X while working on set of files. However, if you try to process a for loop on file name with spaces in them you are going to have some problem. For loop uses $IFS variable to determine what the field separators are. By default $IFS is set to the space character. There are multiple solutions to this problem.
Set $IFS variable
Try it as follows:
#!/bin/bash SAVEIFS=$IFS IFS=$(echo -en "\n\b") for f in * do echo "$f" done IFS=$SAVEIFS
OR
#!/bin/bash SAVEIFS=$IFS IFS=$(echo -en "\n\b") # set me FILES=/data/* for f in $FILES do echo "$f" done # restore $IFS IFS=$SAVEIFS
More examples using $IFS and while loop
Now you know that if the field delimiters are not whitespace, you can set IFS. For example, while loop can be used to get all fields from /etc/passwd file:
.... while IFS=: read userName passWord userID groupID geCos homeDir userShell do echo "$userName -> $homeDir" done < /etc/passwd
Using old good find command to process file names
To process the output of find with a command, try as follows:
find . -print0 | while read -d $'\0' file do echo -v "$file" done
Try to copy files to /tmp with spaces in a filename using find command and shell pipes:
find . -print0 | while read -d $'\0' file; do cp -v "$file" /tmp; done
Processing filenames using an array
Sometimes you need read a file into an array as one array element per line. Following script will read file names into an array and you can process each file using for loop. This is useful for complex tasks:
#!/bin/bash DIR="$1" # failsafe - fall back to current directory [ "$DIR" == "" ] && DIR="." # save and change IFS OLDIFS=$IFS IFS=$'\n' # read all file name into an array fileArray=($(find $DIR -type f)) # restore it IFS=$OLDIFS # get length of an array tLen=${#fileArray[@]} # use for loop read all filenames for (( i=0; i<${tLen}; i++ )); do echo "${fileArray[$i]}" done
Playing mp3s with spaces in file names
Place following code in your ~/.bashrc file:
mp3(){ local o=$IFS IFS=$(echo -en "\n\b") /usr/bin/beep-media-player "$(cat $@)" & IFS=o }
Keep list of all mp3s in a text file such as follows (~/eng.mp3.txt):
/nas/english/Adriano Celentano - Susanna.mp3 /nas/english/Nick Cave & Kylie Minogue - Where The Wild Roses Grow.mp3 /nas/english/Roberta Flack - Kiling Me Softly With This Song.mp3 /nas/english/The Beatles - Girl.mp3 /nas/english/John Lennon - Stand By Me.mp3 /nas/english/The Seatbelts, Cowboy Bebop - 01-Tank.mp3
To play just type:
$ mp3 eng.mp3.txt
🐧 Get the latest tutorials on Linux, Open Source & DevOps via:
- RSS feed or Weekly email newsletter
- Share on Twitter • Facebook • 65 comments... add one ↓
Category | List of Unix and Linux commands |
---|---|
File Management | cat |
Firewall | Alpine Awall • CentOS 8 • OpenSUSE • RHEL 8 • Ubuntu 16.04 • Ubuntu 18.04 • Ubuntu 20.04 |
Network Utilities | dig • host • ip • nmap |
OpenVPN | CentOS 7 • CentOS 8 • Debian 10 • Debian 8/9 • Ubuntu 18.04 • Ubuntu 20.04 |
Package Manager | apk • apt |
Processes Management | bg • chroot • cron • disown • fg • jobs • killall • kill • pidof • pstree • pwdx • time |
Searching | grep • whereis • which |
User Information | groups • id • lastcomm • last • lid/libuser-lid • logname • members • users • whoami • who • w |
WireGuard VPN | Alpine • CentOS 8 • Debian 10 • Firewall • Ubuntu 20.04 |
Every time i forget something and promptly Google for it, I land on your website! Thanks for all your efforts. Brilliant.
Thanks for this. Worked a treat. I wanted to delete dozens of folders in iTunes containing the word “Riddim”, and ended up with this:
SAVEIFS=$IFS
IFS=$(echo -en “nb”)
for d in `ls` ; do g=`ls “$d” | grep Riddim` ; if [ “” != “$g” ] ; then echo “$d – $g” ; fi ; done
IFS=$SAVEIFS
This site should have a bitcoin donation link.
Thanks. I had been trying to do:
> for f in `ls`;
and
> for f in $( ls );
Both were problematic. But
> for f in *; works, regardless of $IFS AFAICT.
Thanks! you’re my savior.
Excelente! Tank You!!!
The filename handling examples are wrong in several ways. Filenames can include newlines, backslashes, backspaces, tabs, asterisks, and other weirdness, which this doesn’t handle.
# For example, wrong – doesn’t handle filenames with n or b
IFS=$(echo -en “nb”)
for f in *
do
echo “$f”
done
For more on how to do it correctly, see:
http://www.dwheeler.com/essays/filenames-in-shell.html
Thank you very much Author for sharing these knowledge with other people!
Can anyone explain to me what the -v’s, used with echo and cp, in the “Using old good find command to process file names” section above, are for/do?
This can be simpler
for f in “` `”
just the ” around the comand and that is it
It’s very helpful. Thank you
Very helpful. Thanks
Nixcrift FTW again. Good job.
DIR=${DIR:-.}
for file in “${filearray[@]}”
do
: do something with “$file” (in quotes)
Hi there,
I used the above example script for concatenating multiple files which works fine for me if the directory structure contains no spaces.However now I am facing a dierctory structure that does have spaces. Setting the IFS doesn’t seem to help here.
The directory structure I am facing is such: ./CH 0000010240/A10/21MT/21mt#001.csv and I want to concatenate all .csv files I find. Here is my current version of the script which I am running on OS X:
Thanks for any hints.
Cheers,M
should be
Hello there,
I tried the code for storing the filenames into an array variable with a few modifications:
But, i have been running into errors on this:
error:
I have no clue how to proceed with this problem. Could anyone tweek this problem?
Thanks.
-A
Thanks for the very clear explanation!
array=($(mysql –host=192.168.1.2 –user=XXXX –password=XXXX -s -N -e ‘use xbmc_video; SELECT c22 FROM movie, files WHERE files.idFile=movie.idFile ORDER BY idMovie DESC LIMIT 10;’))
how can i get rid of spaces in this array
For simple operations on multiple files you can use the -exec option on find:
This command recursively finds all .avi files in the current directory and moves them to folder /Some/Where/Else. The curly brackets {} are substituted with file name. Consult the man page for more details.
Thanks for your tip. I was not thinking anymore about IFS. Here’s a tip to remove unwanted characters in filenames:
replaces ?, % and + with _ (Useful if you copy FAT32 files to linux and back)
Thanks to all contributors of this outstanding discussion!
They do not do the same thing.
The first assigns a newline and a backspace to IFS; the second assigns an empty string.
The first assignment can be done more efficiently with:
What is the difference between
and
Which do exactly the same (but don’t call sub shell)
Thank you very much, this is very helpful.
I work with Linux for many year, but until today, I did not know this.
Trying to get the following to work. If I cut and paste the eventual contents of $directoryFiles EITHER in the bash script OR on the command line, it works perfectly. If I try to get it to do it directly in the script, it fails with:
tar –all the file names– : Cannot stat: No such file or directory
I”ve tried about every expansion or quoting that I can think of for the $direcotryFiles variable.
How about:
In your case:
Fixed my little script! Cheers for posting this – always nice to learn something new.
Hey, thanks for this! I never did much bash scripting and was trying to figure out how to parse an array from a bash RC file into a Perl variable. I started out writing a long parser hack, but trying to support array entries with spaces was a big headache. Then thought maybe bash should do the work instead, and your examples helped a lot. Here’s a one-liner bash script that’s run using the Perl `backtick` operator.
$path is the RC file, and $var is the array variable. BASH variables must be escaped, but PERL
variables must not, and new-line chars have to be added so bash is happy with the string.
First step is to source the RC file, and next get the length of the array.
$list = `. $path; L=${#${var}[@]}n for (( i=0; i<${L}; i++ ));n don echo -n ${${var}[$i]},n done`;
chop( $list ); # strip the trailing comma
foreach $entry (split(",", $list)) { # Prove that it works
print "entry: '$entry'n";
}
Here's an example of the bash array that this will parse. Cool. Thanks again for your examples.
ARRAY_VAR=( .bashrc .vimrc "foo bar" .java
.mozilla
.Xdefaults "other file name" )
Great post! It has been very useful for me 😀
“grep hello $(ls | sed -e “s/ /?/gâ€)”
You don’t need sed or ls; use filename expansion:
grep hello *
“for i in $(find . | sed -e “s/ /?/g) do; ls -l “$iâ€; done”
That will fail if any filenames contain whitespace.
It is also extremely inefficient to call ls in a loop.
My favorite solution, though it can theoretically overmatch:
find . | sed -e “s/ /?/g”
ls | sed -e “s/ /?/g”
Then you can do things like greps, for loops, whatever equally well:
grep hello $(ls | sed -e “s/ /?/g”)
for i in $(find . | sed -e “s/ /?/g) do; ls -l “$i”; done
excellent post. exactly what i was looking for. i had been facing this space-field-seperator problem for quite a sometime today. had tried many options to circumvent it, but in vain. will try this $IFS approach now.
thanks a lot for sharing.
shreyas
for m in "`ls`"; do echo "$m" ; done
Why?
The
for
command will only see one argument.Try this:
n=1
for m in "`ls`"
do
echo "$n"
echo "$m"
n=$(( $n + 1 ))
done
You’ll see that it only goes through the loop once.
You don’t need a loop for that, and you don’t need
ls
:printf "%sn" *
I just use as following
with double quotes
[code]
for m in “`ls`”; do echo “$m” ; done
[/code]
Amazing! Your notation for creating an array from:
> fileArray=($(find $DIR -type f))
is stunning! Thanks for the excellent writeup.
Why “IFS=`echo`”? Command substitution is slow (and unnecessary).
IFS=’
‘
IFS=$’n’ ## bash
Instead of telling echo to print a char for new line and (probably) bell and then *not* to print a new line, one can just use echo’s default new line output 🙂
IFS=`echo`
Thanks for your share!
Thank you! I found this post most useful.
mp3(){
local o=$IFS
IFS=$(echo -en “nb”)
/usr/bin/beep-media-player “$(cat $@)” &
IFS=o
}
“$(cat $@)” if you use this I think the IFS down work …
I think you want $(cat “$@”)
Thanks man, my code is now only 40 lines long, not 136 like before xD^^
nice blog
Chris F.A. Johnson has it right on the nose.
i am doing a project in unix shell scripting.
so my project have messages to be displayed. my problem is that how to add a beep sound to the messages …can any help me to include that also with the coding??pls..its very urgent…i dont know the shell codings…its for my project
Kyle is correct. No need to mess w/ your IFS var.
Your 2nd example is problematic in a very subtle way
FILES=/data/*
does not set the variable ‘FILES’ to the names of all the files in /data, it sets it to ‘/data/*’. You can see this w/:
declare -p FILES
A very useful technique in all this, is to employ the ‘set’ builtin:
cd ..; set *.txt; cd ~-
for f; do
echo "../$f" "$f"
done
This lets you scoop up a file list (w/ nasty names and all), and then move somewhere else to process them.
You can reset the IFS with
unset IFS
this way, you do not need to save the state and then recall it from a variable.
I don’t think there is a need to reset the IFS variable, all you really need to do to avoid problems with word splitting is to put the variables that appear in the loop body in double quotes (I find this to be a good default habit). Once in double quotes the shell will consider whatever the variable expands to as a single argument and pass it to the program as such.
For example:
for i in *.mp3; do rm “$i”; done
mhernandez: Its not a problem with the shell, its just how it works, the bash shell is based on word splitting.
This doesn’t seem to work for me. I’m wondering what I’m doing wrong….
If I do this:
it doesn’t work as I would have expected.
opendir(DIR, “.”) or die(“Error opening directory”);
my @readFiles = readdir(DIR);
foreach (@readFiles) {
…
“All this information is very useful, but I think that needing it is sympton: ¿shouldn’t you use another language instead?”
Why? It’s straightforward shell scripting.
All this information is very useful, but I think that needing it is sympton: ¿shouldn’t you use another language instead?
Nice post, though
I guess you can try
mplayer *.mp3
Or send all files to .playlist.txt
ls -1 > .playlist.txt
mplayer -playlist .playlist.txt
great… vivek.gite ^^
your script just work on one file… then mplayer killed immediately.
yeah just playing one file only(there are many left).
on playing progress i can’t get control of mplayer, even i can’t turn up/down volume, go to the next song, etc.
all i can do is Ctrl+C
btw thanks
# use for loop read all filenames
> for (( i=0; i do
> echo “${fileArray[$i]}”
> done
If all you want to do is print the contents of the array:
printf "%sn" "${fileArray[@]}"
To loop through them, use:
for i in "${fileArray[@]}"
do
: ... whatever
done
> A filename can impossible contain newline-chars, or am I wrong?
A filename can contain newlines. It can contain any character except a slash (/) or an ASCII NUL (0x0).
Try