Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For loop using find lacks doesn't properly handle directory names having white space character

Well i am really stuck at this one.

I have dirs.txt which is as follows:

/var/tmp/old files.txt
/var/tmp/old backups.bak

dirs.txt file is generated by the script itself.

When i want to use dirs.txt in a for loop like:

for dir in `cat dirs.txt` ; do 
    ls -al $dir
done

Here is what the command throws: ls: cannot access /var/tmp/old: No such file or directory

I want the whole filenames to be ls -al'ed but it tries to ls -al /var/tmp/old

How may i correct this from the for loop?

like image 735
GuleLim Avatar asked Jun 27 '09 16:06

GuleLim


1 Answers

cat dirs.txt | while read dir; do
    ls -al "$dir"
done
  1. When you use the backtick operator the output is split into tokens at each whitespace character. read, on the other hand, will read one line at a time rather than one word at a time. So the solution, which looks kind of odd, is to pipe the file into the loop so it can be read line by line.

  2. You need to quote "$dir" in the ls command so that the entire file name, whitespace and all, is treated as a single argument. If you don't do that ls will see two file names, /var/tmp/old and files.txt.

I'm probably going to get awarded the Useless Use of Cat Award. You can simplify this even further by removing the cat:

while read dir; do
    ls -al "$dir"
done < dirs.txt

This works because the entire while loop acts like one big command, so just like you can pipe cat into it, you can also use file redirection.


Taking it even further...

Depending on how dirs.txt is generated you may just be able to get rid of it entirely and do everything in one command. If it's just a temporary file you could eliminate it by piping the command generates dirs.txt into the while loop directly, skipping the temporary file. For instance, replace

find /var -name '*old*' > dirs.txt
while read dir; do
    ls -al "$dir"
done < dirs.txt

with

find /var -name '*old*' | while read dir; do
    ls -al "$dir"
done

Pretend find is whatever command you're doing to generate the file list.

And actually, if you are in fact using find you can probably do everything in one big find command without any loops or anything! For example, the above code using find and while could be done with a single find command like this:

find /var -name '*old*' -exec ls -al {} \;

find is a really flexible command that can both search for files matching all kinds of complicated criteria, and pass those files as command-line arguments to other commands using the -exec option. When you use -exec the {} gets replaced with each file name.

like image 194
John Kugelman Avatar answered Oct 10 '22 13:10

John Kugelman