GuleLim
GuleLim

Reputation: 61

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?

Upvotes: 5

Views: 1653

Answers (2)

hoolahoopzoo
hoolahoopzoo

Reputation: 1

I had a similar problem when I tried to unzip a lot of files in a directory. I found that since I had an alias for my "ls" I was causing myself unnecessary grief. Once I fully-qualified the "ls" to be "/bin/ls" all was well with the world.

As for your specific problem, however, I'm not clear on why you want a file after a directory name when you might want a slash between them (i.e., /var/tmp/old/files.txt).

However, the fact you want to do "ls -al" doesn't fit either. What you would seem to end up doing is a lot of "ls -al /var/tmp/old" (one per dirs.txt entry) and then a long listing of each local file named, as you have shown, files.txt and backups.bak.

Since this thread is nearly 2 years old, however, I imagine you're long past this problem :)

Upvotes: 0

John Kugelman
John Kugelman

Reputation: 361615

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.

Upvotes: 12

Related Questions