Como inverter um loop for?

26

Como faço corretamente um forloop na ordem inversa?

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

Preciso de uma solução que não seja quebrada para caracteres descolados nos nomes dos arquivos.

Mehrdad
fonte
5
Basta canalizar para sort -rantes forou lavar ls -r.
David Schwartz

Respostas:

27

No bash ou no ksh, coloque os nomes dos arquivos em uma matriz e itere sobre essa matriz na 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 no zsh se a ksh_arraysopção estiver configurada (está no modo de emulação ksh). Existe um método mais simples no zsh, que é reverter a ordem das correspondências através de um qualificador global:

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

O POSIX não inclui matrizes; portanto, se você deseja ser portátil, sua única opção para armazenar diretamente uma matriz de cadeias de caracteres 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
Gilles 'SO- parar de ser mau'
fonte
Depois de usar parâmetros posicionais, não há necessidade de suas variáveis ie f; basta executar um shift, usar $1para sua chamada bare testar [ -z $1 ]no seu while.
user1404316
@ user1404316 Essa seria uma maneira excessivamente complicada de iterar os elementos em ordem crescente . Além disso, errado: [ -z $1 ]nem [ -z "$1" ]são testes úteis (e se o parâmetro fosse *, ou uma string vazia?). E, de qualquer forma, eles não ajudam aqui: a questão é como fazer um loop na ordem oposta.
Gilles 'SO- stop be evil'
A questão diz especificamente que está iterando sobre os nomes de arquivo em / var / log, portanto, é seguro assumir que nenhum dos parâmetros estaria "*" ou vazio. Eu permaneço corrigido, no entanto, no ponto da ordem inversa.
user1404316
7

Tente isso, a menos que você considere quebras de linha como "caracteres descolados":

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done
sunaku
fonte
Existe um método que funciona com quebras de linha? (Eu sei que é raro, mas pelo menos por uma questão de aprender Eu gostaria de saber se é possível escrever código correto.)
Mehrdad
Peça criativa para usar tac para reverter o fluxo e, se você quiser se livrar de caracteres indesejados, como quebras de linha, pode canalizar para tr-d '\ n'.
22411 Johan
4
Isso interrompe se os nomes dos arquivos contiverem novas linhas, barras invertidas ou caracteres não imprimíveis. Veja mywiki.wooledge.org/ParsingLs e Como fazer um loop nas linhas de um arquivo? (e os threads vinculados).
Gilles 'SO- stop be evil'
A resposta mais votada quebrou a variável fna minha situação. Essa resposta, porém, funciona praticamente como um substituto para a forlinha comum .
Serge Stroobandt
2

Se alguém está tentando descobrir como reverter a iteração em uma lista de cadeias delimitadas 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
ACK_stoverflow
fonte
2
Eu não tenho tac no meu sistema; Não sei se é robusto, mas estou usando o x em $ {mylist}; revv = "$ {x} $ {revv}"; done
arp 11/01
@arp Isso é meio chocante, como tacfaz parte do coreutils do GNU. Mas sua solução também é boa.
ACK_stoverflow
@ACK_stoverflow Existem sistemas por aí sem as ferramentas de usuário do Linux.
Kusalananda
@Kusalananda Você está dizendo que apenas o Linux usa coreutils GNU? RI MUITO. Até o busybox tem tac. Embora pelo número de tacrespostas -less aqui, eu acho que talvez o OSX não tenha tac. Nesse caso, use alguma variante da solução do @ arp - é mais fácil entender de qualquer maneira.
ACK_stoverflow 16/02
@ACK_stoverflow É o que estou dizendo, sim.
Kusalananda
1
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar

Deve operar da maneira que você deseja (isso foi testado no Mac OS X e eu tenho uma ressalva abaixo ...).

Na 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ê encontra os arquivos que correspondem à sua string + glob e termina cada um com um caractere NUL. Se os seus nomes de arquivo contiverem novas linhas ou outros caracteres estranhos, o find deve lidar com isso muito bem.

tail -r

leva a entrada padrão através do pipe e a inverte (observe que tail -rimprime toda a entrada em stdout, e não apenas as últimas 10 linhas, que é o padrão padrão. man tailpara obter mais informações).

Em seguida, canalizamos isso para 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).

Aqui, o xargs espera ver argumentos separados pelo caractere NUL, dos quais você passou finde revertido tail.

Minha ressalva: li que tailnão funciona bem com seqüências terminadas em nulo . Isso funcionou bem no Mac OS X, mas não posso garantir que esse seja o caso de todos os * nixes. Pisar com cuidado.

Também devo mencionar que o GNU Parallel é frequentemente usado como uma xargsalternativa. Você pode verificar isso também.

Posso estar faltando alguma coisa, para que outras pessoas entrem na conversa.

tcdyl
fonte
2
+1 ótima resposta. Parece que o Ubuntu não suporta tail -r... estou fazendo algo errado?
Mehrdad
11
Não, eu não acho que você é. Eu não tenho minha máquina linux instalada, mas um google rápido para 'linux tail man' não mostra isso como uma opção. sunaku mencionado taccomo uma alternativa, por isso gostaria de tentar que, em vez
tcdyl
Também editei a resposta para incluir outra alternativa
tcdyl
whoops eu esqueci de +1 quando eu disse +1 :( Feito Desculpe por isso haha :)!
Mehrdad
4
tail -ré específico para OSX e reverte a entrada delimitada por nova linha, e não a delimitada por nulo. Sua segunda solução não funciona (você está direcionando a entrada ls, o que não importa); não há uma solução fácil que o faça funcionar de maneira confiável.
Gilles 'SO- stop be evil'
1

No seu exemplo, você está repetindo vários arquivos, mas eu encontrei essa pergunta por causa de seu título mais geral, que também pode abranger um loop sobre uma matriz ou reverter com base em qualquer número de pedidos.

Veja como fazer isso no Zsh:

Se você estiver fazendo um loop sobre os elementos em uma matriz, use esta sintaxe ( origem )

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

Oinverte a ordem especificada no próximo sinalizador; aé a ordem normal da matriz.

Como o @Gilles disse, Ona ordem inversa será de seus arquivos globbed, por exemplo, com my/file/glob/*(On). Isso Onocorre porque é " ordem reversa do nome ".

Sinalizadores de classificação Zsh:

  • a ordem da matriz
  • L comprimento do arquivo
  • l número de links
  • m modificação de data
  • n nome
  • ^oordem inversa ( oé ordem normal)
  • O ordem reversa

Para obter exemplos, consulte https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txt e http://reasoniamhere.com/2014/01/11/outrageously-useful-tips- para dominar seu z-shell /

Henry
fonte
0

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

Essa abordagem não suporta ter novas linhas nos seus nomes de arquivos. A razão subjacente é que tail -rclassifica 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 determinado caractere (digamos, '='), trsubstitua todas as novas linhas para se tornar esse caractere e faça a classificação. O resultado teria a seguinte aparência:

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

Nota: dependendo da sua versão tr, ele pode não ser compatível '\0'como personagem. Geralmente, isso pode ser resolvido alterando o código do idioma para C (mas não me lembro exatamente como, pois depois de corrigi-lo, ele agora funciona no meu computador). Se você receber uma mensagem de erro e não conseguir encontrar a solução alternativa, poste-a como um comentário, para que eu possa ajudá-lo a solucionar o problema.

Alex
fonte
0

uma maneira fácil é usar ls -rcomo mencionado por @David Schwartz

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

Tente o seguinte:

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

Eu acho que é a maneira mais simples.

Syhra
fonte
3
A pergunta foi feita por “ forloop na ordem inversa”.
Manatwork