In the past you may have encountered this problem: looping through a list of files with a Bash “for loop” gave you headaches if filenames had spaces inside. This is our setting:
flevour@voyance:/tmp/blog_post$ touch "my filename with space"
flevour@voyance:/tmp/blog_post$ touch one two three other filenames
flevour@voyance:/tmp/blog_post$ ls -l
total 0
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:49 filenames
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:48 my filename with space
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:49 one
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:49 other
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:49 three
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:49 two
Now, the elite hacker that is in you wants to add a cool extension to all of these. Before getting your hands dirty with mv command, you cleverly echo the variables in the for loop so you know where you are going.
First try:
flevour@voyance:/tmp/blog_post$ for i in `ls`; do echo $i; done
filenames
my
filename
with
space
one
other
three
two
No luck. “Maybe”, you think, “I need to add the -1 (minus-one) switch to have each filename on a single line”, no luck anyway (feel free to try), The fact is Bash “for loop” parses input stream and stops as soon as it finds a “space”. Now if you surf a bit around you may find this solution that, although very compact and easy to remember, doesn’t satisfy your unconsciously deep need for deeper hackery hacknessity:
flevour@voyance:/tmp/blog_post$ ls | while read i; do echo $i; done
filenames
my filename with space
one
other
three
two
This solution correctly loops all the files. But I would like to show you a different approach that is obscure and great at the same time (found at LinuxQuestions):
flevour@voyance:/tmp/blog_post$ IFS=$'\n'
flevour@voyance:/tmp/blog_post$ for i in `ls`; do echo $i; done
filenames
my filename with space
one
other
three
two
Some remarks:
- it’s generally suggested to backup the original content of IFS and restore it when you don’t need the different value anymore
- IFS=$’\n’ looks strange to me, I don’t understand the need for the dollar sign. If you try with IFS=’\n’, you’ll get weird results. Any hackers out there can get the riddle an answer (Rionda? Riff Raff?)
- after some brief researches it’s not clear if IFS modification and restore is seen as a good programming pattern
- stands for Internal Field Separator
Of course the final script (we wanted to add extensions, remember?) would be:
flevour@voyance:/tmp/blog_post$ ifs=$IFS
flevour@voyance:/tmp/blog_post$ IFS=$'\n'
flevour@voyance:/tmp/blog_post$ for i in `ls`; do mv $i $i.1337; done
flevour@voyance:/tmp/blog_post$ IFS=$ifs
Enjoy your Bash hacking time!
theorical help
ok, I don’t have a bash or any other unix shell to try at the moment, but I believe that:
the antipattern
old = CURR
curr= ‘foo’
cmd
CURR=old
should probably become
CURR=‘foo’ cmd
which changes env variable CURR only for that command.
The reason why ‘\n’ doesn’t work is that in most scripting enviroments ‘foo’ means “literally foo”, so IFS=’\n’ would break on a backslash followed by an “n”. Maybe IFS=\n could work
But I’m not sure about most of these things :)
I think the first statement
I think the first statement is valid as long as you can make Bash thing of the “for loop” as a single command, which I am not able to do at the moment.
For your second hint, trying IFS=\n or IFS=”\n” breaks badly:
flevour@voyance:/tmp/test$ for i in `ls`; do echo $i; donemy file
ame with space
o
e
othe
three
two
IFS
On FreeBSD, using the POSIX compliant sh(1) shell, $’\n’ doesn’t work. The UNIX single specification doesn’t speak about this syntax. Anyway this doesn’t mean that $’\n’ should not be allowed, only that it doesn’t work under FreeBSD’s sh. To get the behaviour you need something like:
IFS=`echo` (which is quite good IMHO)
or
IFS=’
‘ (this is only available in a script, and is quite ugly IMHO.
Conclusion: $’\n’ is some kind of bash syntax.
I love the way you get the
I love the way you get the \n with echo :)
Btw, at least in my Bash, if I type IFS=’ and hit return I get the chance to complete the string and close the quote, so it’d be available at command line too. I agree that your second option is quite ugly and that IFS=`echo` is just the most creative solution.
Post new comment