published by flevour on Mon, 07/05/2007 - 18:17
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:
<br />
flevour@voyance:/tmp/blog_post$ touch "my filename with space"<br />
flevour@voyance:/tmp/blog_post$ touch one two three other filenames<br />
flevour@voyance:/tmp/blog_post$ ls -l<br />
total 0<br />
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:49 filenames<br />
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:48 my filename with space<br />
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:49 one<br />
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:49 other<br />
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:49 three<br />
-rw-r--r-- 1 flevour flevour 0 2007-05-07 17:49 two<br />
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:
<br />
flevour@voyance:/tmp/blog_post$ for i in `ls`; do echo $i; done<br />
filenames<br />
my<br />
filename<br />
with<br />
space<br />
one<br />
other<br />
three<br />
two<br />
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:
<br />
flevour@voyance:/tmp/blog_post$ ls | while read i; do echo $i; done<br />
filenames<br />
my filename with space<br />
one<br />
other<br />
three<br />
two<br />
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):
<br />
flevour@voyance:/tmp/blog_post$ IFS=$'\n'<br />
flevour@voyance:/tmp/blog_post$ for i in `ls`; do echo $i; done<br />
filenames<br />
my filename with space<br />
one<br />
other<br />
three<br />
two<br />
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:
<br />
flevour@voyance:/tmp/blog_post$ ifs=$IFS<br />
flevour@voyance:/tmp/blog_post$ IFS=$'\n'<br />
flevour@voyance:/tmp/blog_post$ for i in `ls`; do mv $i $i.1337; done<br />
flevour@voyance:/tmp/blog_post$ IFS=$ifs<br />
Enjoy your Bash hacking time!
Comments
Anonymous (not verified)
Mon, 07/05/2007 - 20:30
Permalink
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 :)
flevour
Wed, 09/05/2007 - 14:34
Permalink
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; done<br /> my file<br /> ame with space<br /> o<br /> e<br /> othe<br /> three<br /> twoAnonymous (not verified)
Mon, 07/05/2007 - 22:07
Permalink
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.
flevour
Wed, 09/05/2007 - 14:28
Permalink
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.