How do I reverse a for loop?
Solution 1
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
Solution 2
Try this, unless you consider line breaks as "funky characters":
ls /var/logs/foo*.log | tac | while read f; do
bar "$f"
done
Solution 3
If anyone is trying to figure out how to reverse iterate over a space-delimited string list, this works:
reverse() {
tac <(echo "$@" | tr ' ' '\n') | tr '\n' ' '
}
list="a bb ccc"
for i in `reverse $list`; do
echo "$i"
done
> ccc
> bb
> a
Solution 4
In your example you're looping over several files, but I found this question because of its more general title which could also cover looping over an array, or reversing based on any number of orders.
Here's how to do that in Zsh:
If you're looping over the elements in an array, use this syntax (source)
for f in ${(Oa)your_array}; do
...
done
O
reverses of the order specified in the next flag; a
is the normal array order.
As @Gilles said, On
will reverse order your globbed files, e.g. with my/file/glob/*(On)
. That's because On
is "reverse name order."
Zsh sort flags:
-
a
array order -
L
file length -
l
number of links -
m
modification date -
n
name -
^o
reverse order (o
is normal order) -
O
reverse order
For examples, see https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txt and http://reasoniamhere.com/2014/01/11/outrageously-useful-tips-to-master-your-z-shell/
Solution 5
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.
Related videos on Youtube
user541686
Updated on September 18, 2022Comments
-
user541686 over 1 year
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.
-
ChrisCornwall over 12 yearsJust pipe to
sort -r
before thefor
, or launder throughls -r
.
-
-
user541686 over 12 years+1 great answer. It seems like Ubuntu doesn't support
tail -r
though... am I doing something wrong? -
Johan over 12 yearsCreative 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'.
-
Ignux02 over 12 yearsNo, 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 -
Ignux02 over 12 yearsI've also edited the answer to include another alternative
-
user541686 over 12 yearswhoops I forgot to +1 when I said +1 :( Done! Sorry about that haha :)
-
Gilles 'SO- stop being evil' over 12 yearsThis 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 'SO- stop being evil' over 12 years
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 tols
, which doesn't care); there is no easy fix that would make it work reliably. -
manatwork over 10 yearsThe question asked for “
for
loop in reverse order”. -
Serge Stroobandt almost 9 yearsThe most voted answer broke the variable
f
in my situation. This answer though, acts pretty much as a drop-in replacement for the ordinaryfor
line. -
Alex almost 7 yearsOn OSX,
tail -r
reverses newline-delimited input. Since yourfind
returns null-delimited input,tail -r
does not do anything to it at all. If you omit-print0
, however, then find will return newline-delimited input, sotail -r
will work. But in that case, your solution is no better than simply piping the output ofls -1
intotail -r
. -
Alex almost 7 yearsThis solution only works when bar is a single executable. It does not work, for example, with loops.
-
arp over 6 yearsI don't have tac on my system ; I don't know if it's robust but I've been using for x in ${mylist}; do revv="${x} ${revv}"; done
-
ACK_stoverflow over 6 years@arp That's sort of shocking as
tac
is part of GNU coreutils. But your solution is also a good one. -
Kusalananda over 6 years@ACK_stoverflow There are systems out there without Linux userland tools.
-
user1404316 over 6 yearsOnce you're using positional parameters, there's no need for your variables
i
andf
; just perform ashift
, use$1
for your call tobar
, and test on[ -z $1 ]
in yourwhile
. -
Gilles 'SO- stop being evil' over 6 years@user1404316 This would be an overly complicated way of iterating over the elements in ascending order. Also, wrong: neither
[ -z $1 ]
nor[ -z "$1" ]
are useful tests (what if the parameter was*
, or an empty string?). And in any case they don't help here: the question is how to loop in the opposite order. -
user1404316 over 6 yearsThe question specifically says that it is iterating over file names in /var/log, so it's safe to assume that none of the parameters would be "*" or empty. I do stand corrected, though, on the point of the reverse order.
-
Kusalananda over 6 years@ACK_stoverflow That's what I'm saying, yes.
-
Taher Ghaleb about 2 yearsInstead of
tac
, just dotail -r
. -
ACK_stoverflow about 2 years@TaherGhaleb Idk what version of
tail
is on your system but no linux I have access to (several debian versions and an embedded system) hastail -r
. Guessingtail -r
is a mac thing. -
Taher Ghaleb about 2 yearsOh yeah, sorry for not mentioning that : ) Thanks.