tags:

views:

139

answers:

7

I'm trying to write a bash script that will process a list of files whose names are stored one per line in an input file, something the likes of

find . -type f -mtime +15 > /tmp/filelist.txt
for F in $(cat /tmp/filelist.txt) ; do
  ...
done;

My problem is that filenames in filelist.txt may contain spaces, so the snipped above will expand the line

my text file.txt

to three different filenames, my, text and file.txt. How can I fix that?

A: 

I believe you can skip the temporary file entirely and just directly iterate over the results of find, i.e.:

for F in $(find . -type f -mtime +15) ; do
  ...
done;

No guarantees that my syntax is correct but I'm pretty sure the concept works.

Edit: If you really do have to process the file with a list of filenames and can't simply combine the commands as I did above, then you can change the value of the IFS variable--it stands for Internal Field Separator--to change how bash determines fields. By default it is set to whitespace, so a newline, space, or tab will begin a new field. If you set it to contain only a newline, then you can iterate over the file just as you did before.

qid
I think the shell will still do separation on white-space?
Douglas Leeder
Please downvote this - it has the SAME problem as the original (splits on spaces)
DVK
I made an edit and had to take a phone call in the middle, but I guess 5 minutes is too long to wait on this site.
qid
+2  A: 

Use read:

while read F  ; do
        echo $F
done </tmp/filelist.txt

Alternatively use IFS to change how the shell separates your list:

OLDIFS=$IFS
IFS="
"
for F in $(cat /tmp/filelist.txt) ; do
  echo $F
done
IFS=$OLDIFS

Alternatively (as suggested by @tangens), convert the body of your loop into a separate script, then use find's -exec option to run if for each file found directly.

Douglas Leeder
+1  A: 

use while read

echo $FILE | while read line
do
echo $line
done

You can do redirect instead of echo

DVK
If you've only got one file, then a shell redirect avoids spawning a `cat` process.
Douglas Leeder
A: 

I'm not a bash expert by any means ( I usually write my script in ruby or python to be cross-platform), but I would use a regex expration to escape spaces in each line before you process it.

For Bash Regex: http://www.linuxjournal.com/node/1006996

In a similar situation in Ruby ( processing a csv file, and cleaning up each line before using it):

File.foreach(csv_file_name) do |line| 
    clean_line = line.gsub(/( )/, '\ ') 
    #this finds the space in your file name and escapes it    
    #do more stuff here
end
Nick Gorbikoff
+2  A: 

You could use the -exec parameter of find and use the file names directly:

find . -type f -mtime +15 -exec <your command here> {} \;

The {} is a placeholder for the file name.

tangens
The problem is that I'm not running a single command and I would end up trying all types of weird combinations to find the right way to quote and escape the stuff after -exec
agnul
+2  A: 

You can do this without a temporary file using process substitution:

while read F
do
  ...
done < <(find . -type f -mtime +15)
Dennis Williamson
Neat! Didn't know about process substitution.
agnul
Note that the process substitution feature is a bash extension, and is not even available with bash in sh-compatibility mode. You must start your script with `#!/bin/bash` for it to work. BTW, I also recommend using `while IFS="" read -r F` to avoid possible problems with whitespace at the beginning or end of filenames, and backslashes at the end (although if the files are coming from Windows, backslashes are probably not possible).
Gordon Davisson
A: 

pipe your find command straight to while read loop

find . -type f -mtime +15 | while read -r line
do
   printf "do something with $line\n"
done
ghostdog74