Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

How do I properly do a for loop in reverse order?

for f in /var/logs/foo*.log; do
    bar "$f"
done

I need a solution that doesn't break for funky characters in the file names.

share|improve this question
1  
Just pipe to sort -r before the for, or launder through ls -r. –  David Schwartz Dec 22 '11 at 4:59
add comment

4 Answers

In bash or ksh, put the file names in an array, and iterate over that array in reverse order.

files=(/var/logs/foo*.log)
for ((i=${#files[@]}-1; i>=0; i--)); do
  bar "${files[$i]}"
done

The code above also works in zsh if the ksh_arrays option is set (it is in ksh emulation mode). There's a simpler method in zsh, which is to reverse the order of the matches through a glob qualifier:

for f in /var/logs/foo*.log(On); do bar $f; done

POSIX doesn't include arrays, so if you want to be portable, your only option to directly store an array of strings is the positional parameters.

set -- /var/logs/foo*.log
i=$#
while [ $i -gt 0 ]; do
  eval "f=\${$i}"
  bar "$f"
  i=$((i-1))
done
share|improve this answer
add comment

Try this, unless you consider line breaks as "funky characters":

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done
share|improve this answer
    
Is there a method that works with line breaks? (I know it's rare, but at least for the sake of learning I'd like to know if it's possible to write correct code.) –  Mehrdad Dec 22 '11 at 5:04
    
Creative to use tac to reverse the flow, and if you like to get rid of some unwanted characters like line breaks you can pipe to tr -d '\n'. –  Johan Dec 22 '11 at 6:07
3  
This breaks if the file names contain newlines, backslashes or unprintable characters. See mywiki.wooledge.org/ParsingLs and How to loop over the lines of a file? (and the linked threads). –  Gilles Dec 22 '11 at 8:26
add comment
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar

Should operate the way you want (this was tested on Mac OS X and I have a caveat below...).

From the man page for find:

-print0
         This primary always evaluates to true.  It prints the pathname of the current file to standard output, followed by an ASCII NUL character (charac-
         ter code 0).

Basically, you're finding the files that match your string + glob and terminating each with a NUL character. If your filenames contain newlines or other strange characters, find should handle this well.

tail -r

takes the standard input through the pipe and reverses it (note that tail -r prints all of the input to stdout, and not just the last 10 lines, which is the standard default. man tail for more info).

We then pipe that to xargs -0 :

-0      Change xargs to expect NUL (``\0'') characters as separators, instead of spaces and newlines.  This is expected to be used in concert with the
         -print0 function in find(1).

Here, xargs expects to see arguments separated by the NUL character, which you passed from find and reversed with tail.

My caveat: I've read that tail doesn't play well with null-terminated strings. This worked well on Mac OS X, but I can't guarantee that's the case for all *nixes. Tread carefully.

I should also mention that GNU Parallel is often used as an xargs alternative. You may check that out, too.

I may be missing something, so others should chime in.

share|improve this answer
1  
+1 great answer. It seems like Ubuntu doesn't support tail -r though... am I doing something wrong? –  Mehrdad Dec 22 '11 at 6:02
1  
No, I don't think you are. I don't have my linux machine up but a quick google for 'linux tail man' doesn't show it as an option. sunaku mentioned tac as an alternative, so I would try that, instead –  tcdyl Dec 22 '11 at 6:17
    
I've also edited the answer to include another alternative –  tcdyl Dec 22 '11 at 7:01
    
whoops I forgot to +1 when I said +1 :( Done! Sorry about that haha :) –  Mehrdad Dec 22 '11 at 7:22
2  
tail -r is specific to OSX, and reverses newline-delimited input, not null-delimited input. Your second solution doesn't work at all (you're piping input to ls, which doesn't care); there is no easy fix that would make it work reliably. –  Gilles Dec 22 '11 at 8:31
add comment

Try this:

for f in /var/logs/foo*.log; do
bar "$f"
done

I think it is the most simple way.

share|improve this answer
1  
The question asked for “for loop in reverse order”. –  manatwork Oct 9 '13 at 7:52
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.