如何反转for循环?


26

如何正确地做了for相反的顺序循环?

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

我需要一种不会破坏文件名中时髦字符的解决方案。


5
只需在sort -r之前移至for或清洗即可ls -r
大卫·史瓦兹

Answers:


27

在bash或ksh中,将文件名放入一个数组中,并以相反的顺序遍历该数组。

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

如果ksh_arrays设置了该选项(在ksh仿真模式下),则上面的代码也可以在zsh中工作。zsh中有一个更简单的方法,该方法是通过glob限定符反转匹配的顺序:

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

POSIX不包含数组,因此,如果您希望具有可移植性,则直接存储字符串数组的唯一选择就是位置参数。

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

使用位置参数后,就不需要变量i和了f。只是执行shift,使用$1为您的来电bar,并测试[ -z $1 ]你的while
user1404316 '18

@ user1404316这将是一种过度复杂的以升序迭代元素的方式。同样,这是错误的:测试[ -z $1 ]也不[ -z "$1" ]是有用的测试(参数为*还是空字符串会怎样?)。无论如何,它们都无济于事:问题是如何以相反的顺序循环。
吉尔斯(Gillles)“所以别再邪恶了”

该问题专门说它正在/ var / log中遍历文件名,因此可以安全地假设所有参数都不是“ *”或为空。不过,我确实站在相反的顺序上。
user1404316

7

除非您认为换行符是“笨拙的字符”,否则请尝试以下操作:

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done

有没有适合换行符的方法?(我知道这很罕见,但是至少为了学习,我想知道是否有可能编写正确的代码。)
Mehrdad

创造性地使用tac来逆向流程,如果您希望摆脱一些不需要的字符(例如换行符),可以将其传递给tr -d'\ n'。
约翰

4
如果文件名包含换行符,反斜杠或不可打印字符,则此操作会中断。请参阅mywiki.wooledge.org/ParsingLs如何循环遍历文件行?(以及链接的线程)。
吉尔斯(Gilles)'所以

f在我的情况下,投票率最高的答案打破了变数。不过,这个答案几乎可以替代普通产品for线。
Serge Stroobandt

2

如果有人试图弄清楚如何对以空格分隔的字符串列表进行反向迭代,则可以这样做:

reverse() {
  tac <(echo "$@" | tr ' ' '\n') | tr '\n' ' '
}

list="a bb ccc"

for i in `reverse $list`; do
  echo "$i"
done
> ccc
> bb 
> a

2
我的系统上没有TAC;我不知道它是否健壮,但我一直在$ {mylist}中使用x;做revv =“ $ {x} $ {revv}”; 完成
arp

@arp就像tacGNU coreutils的一部分一样令人震惊。但是您的解决方案也是一个很好的解决方案。
ACK_stoverflow,

@ACK_stoverflow有些系统没有Linux用户界面工具。
库沙兰丹

@Kusalananda您是说只有Linux使用GNU coreutils吗?大声笑。甚至busybox也有tac。尽管从tac这里的-less响应数量来看,我猜想OSX可能没有tac。在这种情况下,请使用@arp解决方案的某些变体-无论如何都更容易理解。
ACK_stoverflow '18

@ACK_stoverflow我就是这么说的,是的。
库沙兰丹

1
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar

应该可以按照您想要的方式进行操作(这已经在Mac OS X上进行了测试,下面有一个警告……)。

从手册页中查找:

-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).

基本上,您会找到与您的字符串+ glob匹配的文件,并以NUL字符结尾。如果您的文件名包含换行符或其他奇怪的字符,find应该可以很好地解决这个问题。

tail -r

采用标准输入通过管道和逆转它(注意,tail -r打印所有输入到stdout,而不是仅在过去的10行,这是标准的默认。man tail获取更多信息)。

然后,我们将其传送到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).

在这里,xargs期望看到由NUL字符分隔的参数,您可以从中传递该参数并将其取findtail

我的告诫:我读过,tail以null终止的字符串效果不佳。这在Mac OS X上效果很好,但是我不能保证所有* nix都是如此。小心踩一下。

我还应该提到,经常使用GNU Parallel作为xargs替代。您也可以检查一下。

我可能想念一些东西,所以其他人也应该插话。


2
+1好答案。看来Ubuntu虽然不支持tail -r...我做错什么了吗?
Mehrdad

1
不,我不认为你是。我没有安装我的linux机器,但是对于“ linux tail man”的快速搜索引擎没有将其显示为选项。提到tac了sunaku 作为替代方案,所以我会尝试这样做
tcdyl 2011年

我还编辑了答案,以包括另一种选择
tcdyl 2011年

哎呀,当我说+1时我忘了+1 :(完成!对不起,哈哈:)
Mehrdad

4
tail -r特定于OSX,并反转换行符分隔的输入,而不是空分隔符的输入。您的第二个解决方案根本不起作用(您正在将输入传递给ls,这无关紧要);没有简单的修补程序可以使它可靠地工作。
吉尔(Gilles)“所以,别再邪恶了”,

1

在您的示例中,您正在遍历多个文件,但由于存在更笼统的标题(可能涵盖遍历数组或根据任意数量的顺序进行求逆),所以我发现了这个问题。

这是在Zsh中执行此操作的方法:

如果要遍历数组中的元素,请使用以下语法(source

for f in ${(Oa)your_array}; do
    ...
done

O与下一个标志中指定的顺序相反;a是正常的数组顺序。

就像@Gilles所说的那样,On将反转您的文件,例如使用my/file/glob/*(On)。那是因为On是“反向名称顺序”。

Zsh排序标志:

  • a 排列顺序
  • L 文件长度
  • l 链接数
  • m 修改日期
  • n 名称
  • ^o逆序(o正常顺序)
  • O 相反的顺序

有关示例,请参见https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txthttp://reasoniamhere.com/2014/01/11/outrageously-useful-tips-掌握您的Z-shell /


0

Mac OSX不支持该tac命令。当您在for循环中调用单个命令时,@ tcdyl的解决方案有效。对于所有其他情况,以下是解决该问题的最简单方法。

此方法不支持文件名中包含换行符。根本原因是,tail -r它按换行符分隔输入内容。

for i in `ls -1 [filename pattern] | tail -r`; do [commands here]; done

但是,有一种方法可以解决换行限制。如果您知道文件名不包含某个字符(例如'='),则可以使用tr替换所有换行符以使其成为该字符,然后进行排序。结果如下所示:

for i in `find [directory] -name '[filename]' -print0 | tr '\n' '=' | tr '\0' '\n'
| tail -r | tr '\n' '\0' | tr '=' '\n' | xargs -0`; do [commands]; done

注意:根据您的版本tr,它可能不支持'\0'作为字符。通常可以通过将语言环境更改为C来解决此问题(但我不记得具体如何,因为修复一次后,它现在就可以在我的计算机上使用了)。如果收到错误消息,但找不到解决方法,请将其作为注释发布,以便我们帮助您进行故障排除。


0

ls -r如@David Schwartz所述,一种简单的方法是使用

for f in $(ls -r /var/logs/foo*.log); do
    bar "$f"
done

-4

尝试这个:

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

我认为这是最简单的方法。


3
该问题要求“ for以相反顺序循环”。
manatwork
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.