views:

268

answers:

2

Hi everyone.. 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?

+8  A: 
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.

John Kugelman
Note that there's a potential gotcha with any of the "somecommand | while read..." versions: the while loop runs in a subshell, so any variables set in it vanish as soon as it exits. The "while ... done <dirs.txt" versions don't have this problem. See http://mywiki.wooledge.org/BashFAQ/024 for some workarounds.
Gordon Davisson
A: 

See this answer to your previous question. I know it's a switch from what you're currently doing, but it may save you some grief.

Brian Agnew