Como faço para reverter um loop for?

25

Como faço corretamente fazer um loop for em ordem reversa?

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

Eu preciso de uma solução que não quebre os caracteres no nome dos arquivos.

    
por Mehrdad 22.12.2011 / 05:46

7 respostas

25

No bash ou ksh, coloque os nomes dos arquivos em uma matriz e itere-os em ordem inversa.

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

O código acima também funciona em zsh se a opção ksh_arrays estiver configurada (está no modo de emulação ksh). Existe um método mais simples em zsh, que é inverter a ordem das correspondências por meio de um qualificador glob:

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

O POSIX não inclui matrizes, portanto, se você deseja ser portável, sua única opção para armazenar diretamente uma matriz de strings são os parâmetros posicionais.

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

Tente isso, a menos que você considere quebra de linha como "caracteres engraçados":

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done
    
por 22.12.2011 / 06:01
1
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar

Deve funcionar do jeito que você quer (isso foi testado no Mac OS X e eu tenho uma ressalva abaixo ...).

Da página do manual para encontrar:

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

Basicamente, você está encontrando os arquivos que correspondem à sua string + glob e terminando cada um com um caractere NUL. Se os seus nomes de arquivos contiverem novas linhas ou outros caracteres estranhos, o find deve lidar com isso bem.

tail -r

pega a entrada padrão através do pipe e a inverte (observe que tail -r imprime todos da entrada para stdout, e não apenas as últimas 10 linhas, que é o padrão. man tail para mais informações).

Em seguida, encaminhamos isso para xargs -0 :

-0      Change xargs to expect NUL (''
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar
'') characters as separators, instead of spaces and newlines. This is expected to be used in concert with the -print0 function in find(1).

Aqui, xargs espera ver os argumentos separados pelo caractere NUL, que você passou de find e inverteu com tail .

Minha advertência: Eu li que tail não joga bem com strings com terminação nula . Isso funcionou bem no Mac OS X, mas não posso garantir que isso seja o caso de todos os * nixes. Pise com cuidado.

Eu também devo mencionar que GNU Parallel é frequentemente usado como uma alternativa xargs . Você pode verificar isso também.

Eu posso estar perdendo alguma coisa, então outros devem entrar em contato.

    
por 22.12.2011 / 06:51
1

Se alguém estiver tentando descobrir como reverter a iteração em uma lista de strings delimitada por espaço, isso funciona:

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

list="a bb ccc"

for i in 'reverse $list'; do
  echo "$i"
done
> ccc
> bb 
> a
    
por 25.05.2017 / 21:57
0

O Mac OSX não suporta o comando tac . A solução de @tcdyl funciona quando você está chamando um único comando em um loop for . Para todos os outros casos, o seguinte é a maneira mais simples de contornar isso.

Esta abordagem não suporta ter novas linhas nos seus nomes de arquivos. A razão subjacente é que tail -r classifica sua entrada como delimitada por novas linhas.

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

No entanto, existe uma maneira de contornar a limitação da nova linha. Se você souber que seus nomes de arquivos não contêm um caractere específico (digamos, '='), então você pode usar tr para substituir todas as novas linhas para se tornar esse caractere e, em seguida, fazer a classificação. O resultado seria o seguinte:

for i in 'find [directory] -name '[filename]' -print0 | tr '\n' '=' | tr '
for i in 'ls -1 [filename pattern] | tail -r'; do [commands here]; done
' '\n' | tail -r | tr '\n' '
for i in 'find [directory] -name '[filename]' -print0 | tr '\n' '=' | tr '%pre%' '\n'
| tail -r | tr '\n' '%pre%' | tr '=' '\n' | xargs -0'; do [commands]; done
' | tr '=' '\n' | xargs -0'; do [commands]; done

Nota: dependendo da sua versão de tr , talvez não ofereça suporte a '%code%' como um caractere. Isso geralmente pode ser trabalhado alterando a localidade para C (mas não me lembro exatamente como, depois de corrigi-lo, uma vez que agora funciona no meu computador). Se você receber uma mensagem de erro e não conseguir encontrar a solução alternativa, poste-a como comentário para que possamos ajudá-lo a solucionar o problema.

    
por 10.07.2017 / 18:50
0

No seu exemplo, você está passando por vários arquivos, mas encontrei essa questão por causa de seu título mais geral, que também pode cobrir o loop de uma matriz ou reverter com base em qualquer número de pedidos.

Veja como fazer isso no Zsh:

Se você estiver circulando os elementos em uma matriz, use esta sintaxe ( fonte )

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

O inverte a ordem especificada no próximo sinalizador; a é a ordem normal das matrizes.

Como @Gilles disse, On irá inverter a ordem dos seus arquivos globbed, por exemplo com my/file/glob/*(On) . Isso porque On é a ordem "reversa nome ".

Sinalizadores de classificação do Zsh:

  • a ordem de matriz
  • L tamanho do arquivo
  • l número de links
  • m data de modificação
  • n name
  • ^o ordem inversa ( o é ordem normal)
  • O ordem inversa

Por exemplo, veja link e link

    
por 15.02.2018 / 18:56
-4

Tente isto:

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

Eu acho que é a maneira mais simples.

    
por 09.10.2013 / 09:36