views:

264

answers:

3

Suppose I've got a list of files

file1
"file 1"
file2

a for...in loop breaks it up between whitespace, not newlines:

for x in $( ls ); do
   echo $x
done

results:

file
1
file1
file2

I want to execute a command on each file. "file" and "1" above are not actual files. How can I do that if the filenames contains things like spaces or commas?

It's a little trickier than I think find -print0 | xargs -0 could handle, because I actually want the command to be something like "convert input/file1.jpg .... output/file1.jpg" so I need to permutate the filename in the process.

+2  A: 

one possible way:

ls -1 | while read x; do
   echo $x
done
eduffy
+5  A: 

Actually, Mark's suggestion works fine without even doing anything to the internal field separator. The problem is running ls in a subshell, whether by backticks or $( ) causes the for loop to be unable to distinguish between spaces in names. Simply using

for f in *

instead of the ls solves the problem.

#!/bin/bash
for f in *
do
 echo "$f"
done
Jordan
A: 

I know this one is LONG past "answered", and with all due respect to eduffy, I came up with a better way and I thought I'd share it.

What's "wrong" with eduffy's answer isn't that it's wrong, but that it imposes what for me is a painful limitation: there's an implied creation of a subshell when the output of the ls is piped and this means that variables set inside the loop are lost after the loop exits. Thus, if you want to write some more sophisticated code, you have a pain in the buttocks to deal with.

My solution was to take the "readline" function and write a program out of it in which you can specify any specific line number that you may want that results from any given function call. ... As a simple example, starting with eduffy's:

ls_output=$(ls -1)
# The cut at the end of the following line removes any trailing new line character
declare -i line_count=$(echo "$ls_output" | wc -l | cut -d ' ' -f 1)
declare -i cur_line=1 
while [ $cur_line -le $line_count ] ;
do  
   # NONE of the values in the variables inside this do loop are trapped here.
   filename=$(echo "$ls_output" | readline -n $cur_line)
   # Now line contains a filename from the preceeding ls command
   cur_line=cur_line+1
done

Now you have wrapped up all the subshell activity into neat little contained packages and can go about your shell coding without having to worry about the scope of your variable values getting trapped in subshells.

I wrote my version of readline in gnuc if anyone wants a copy, it's a little big to post here, but maybe we can find a way...

Hope this helps, RT

Richard T