Comment inverser une boucle for?

26

Comment faire correctement une forboucle dans l'ordre inverse?

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

J'ai besoin d'une solution qui ne casse pas pour les caractères géniaux dans les noms de fichiers.

Mehrdad
la source
5
Il suffit de passer la pipe sort -ravant forou de la laver ls -r.
David Schwartz

Réponses:

27

Dans bash ou ksh, placez les noms de fichiers dans un tableau et parcourez ce tableau dans l'ordre inverse.

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

Le code ci-dessus fonctionne également en zsh si l' ksh_arraysoption est définie (elle est en mode d'émulation ksh). Il existe une méthode plus simple dans zsh, qui consiste à inverser l'ordre des correspondances via un qualificatif glob:

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

POSIX n'inclut pas de tableaux, donc si vous voulez être portable, votre seule option pour stocker directement un tableau de chaînes est les paramètres positionnels.

set -- /var/logs/foo*.log
i=$#
while [ $i -gt 0 ]; do
  eval "f=\${$i}"
  bar "$f"
  i=$((i-1))
done
Gilles 'SO- arrête d'être méchant'
la source
Une fois que vous utilisez des paramètres positionnels, vous n'avez plus besoin de vos variables iet f; effectuez simplement une shift, utilisez $1pour votre appel baret testez [ -z $1 ]votre while.
user1404316
@ user1404316 Ce serait une manière trop compliquée d'itérer sur les éléments dans l'ordre croissant . Également faux: ni les tests utiles [ -z $1 ]ni [ -z "$1" ]les tests (que faire si le paramètre était *, ou une chaîne vide?). Et en tout cas, cela n'aide pas ici: la question est de savoir comment boucler dans l'ordre inverse.
Gilles 'SO- arrête d'être méchant'
La question indique spécifiquement qu'il itère sur les noms de fichiers dans / var / log, il est donc sûr de supposer qu'aucun des paramètres ne serait "*" ou vide. Je suis cependant corrigé sur le point de l'ordre inverse.
user1404316
7

Essayez ceci, sauf si vous considérez les sauts de ligne comme des "personnages géniaux":

ls /var/logs/foo*.log | tac | while read f; do
    bar "$f"
done
sunaku
la source
Existe-t-il une méthode qui fonctionne avec les sauts de ligne? (Je sais que c'est rare, mais au moins pour le plaisir d'apprendre, j'aimerais savoir s'il est possible d'écrire le code correct.)
Mehrdad
Créatif pour utiliser tac pour inverser le flux, et si vous souhaitez vous débarrasser de certains caractères indésirables comme les sauts de ligne, vous pouvez diriger vers tr -d '\ n'.
Johan
4
Cela se rompt si les noms de fichier contiennent des retours à la ligne, des barres obliques inverses ou des caractères non imprimables. Voir mywiki.wooledge.org/ParsingLs et Comment boucler sur les lignes d'un fichier? (et les fils liés).
Gilles 'SO- arrête d'être méchant'
La réponse la plus votée a brisé la variable fdans ma situation. Cette réponse, cependant, agit à peu près comme un remplacement sans rendez-vous pour la forligne ordinaire .
Serge Stroobandt
2

Si quelqu'un essaie de comprendre comment inverser l'itération sur une liste de chaînes délimitées par des espaces, cela fonctionne:

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
la source
2
Je n'ai pas de tac sur mon système; Je ne sais pas si c'est robuste mais j'utilise for x dans $ {mylist}; do revv = "$ {x} $ {revv}"; fait
arp
@arp C'est un peu choquant comme cela tacfait partie de GNU coreutils. Mais votre solution est également bonne.
ACK_stoverflow
@ACK_stoverflow Il existe des systèmes sans outils utilisateur Linux.
Kusalananda
@Kusalananda Êtes-vous en train de dire que seul Linux utilise des coreutils GNU? LOL. Même busybox l'a fait tac. Bien que d'après le nombre de tacréponses sans réponse ici, je suppose que OSX n'a ​​peut-être pas de tac. Dans ce cas, utilisez une variante de la solution de @ arp - c'est plus facile à comprendre de toute façon.
ACK_stoverflow
@ACK_stoverflow C'est ce que je dis, oui.
Kusalananda
1
find /var/logs/ -name 'foo*.log' -print0 | tail -r | xargs -0 bar

Devrait fonctionner comme vous le souhaitez (cela a été testé sur Mac OS X et j'ai une mise en garde ci-dessous ...).

Depuis la page de manuel pour trouver:

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

Fondamentalement, vous trouvez les fichiers qui correspondent à votre chaîne + glob et vous terminez chacun par un caractère NUL. Si vos noms de fichiers contiennent des sauts de ligne ou d'autres caractères étranges, find devrait bien gérer cela.

tail -r

prend l'entrée standard à travers le tuyau et l'inverse (notez que toutes les entrées sont tail -rimprimées sur stdout, et pas seulement les 10 dernières lignes, ce qui est la valeur par défaut standard. pour plus d'informations).man tail

Nous les canalisons ensuite pour 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).

Ici, xargs s'attend à voir des arguments séparés par le caractère NUL, avec lequel vous avez passé findet inversé tail.

Ma mise en garde: j'ai lu que tailcela ne fonctionne pas bien avec des chaînes terminées par null . Cela fonctionnait bien sur Mac OS X, mais je ne peux pas garantir que c'est le cas pour tous les * nix. Faites preuve de prudence.

Je dois également mentionner que GNU Parallel est souvent utilisé comme xargsalternative. Vous pouvez également vérifier cela.

Il me manque peut-être quelque chose, alors les autres devraient sonner.

tcdyl
la source
2
+1 bonne réponse. Il semble cependant qu'Ubuntu ne prend pas en charge tail -r... est-ce que je fais quelque chose de mal?
Mehrdad
1
Non, je ne pense pas que tu l'es. Je n'ai pas ma machine Linux en place mais un rapide google pour 'linux tail man' ne l'affiche pas en option. sunaku mentionné taccomme alternative, donc j'essaierais plutôt
tcdyl
J'ai également modifié la réponse pour inclure une autre alternative
tcdyl
whoops j'ai oublié de +1 quand j'ai dit +1 :( Terminé! Désolé pour ça haha ​​:)
Mehrdad
4
tail -rest spécifique à OSX et inverse les entrées délimitées par des sauts de ligne, et non les entrées délimitées par des valeurs nulles. Votre deuxième solution ne fonctionne pas du tout (vous canalisez l'entrée vers ls, ce qui ne fait rien); il n'y a pas de solution facile qui le rendrait fiable.
Gilles 'SO- arrête d'être méchant'
1

Dans votre exemple, vous bouclez sur plusieurs fichiers, mais j'ai trouvé cette question en raison de son titre plus général qui pourrait également couvrir le bouclage sur un tableau ou l'inversion en fonction d'un certain nombre de commandes.

Voici comment faire cela dans Zsh:

Si vous faites une boucle sur les éléments d'un tableau, utilisez cette syntaxe ( source )

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

Oinverse l'ordre spécifié dans le drapeau suivant; aest l'ordre normal des tableaux.

Comme l'a dit @Gilles, Oninversera l'ordre de vos fichiers globalisés, par exemple avec my/file/glob/*(On). C'est parce que Onc'est "l' ordre inverse des noms ".

Indicateurs de tri Zsh:

  • a ordre du tableau
  • L longueur du fichier
  • l nombre de liens
  • m date de modification
  • n prénom
  • ^oordre inverse ( oest un ordre normal)
  • O ordre inverse

Pour des exemples, voir https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txt et http://reasoniamhere.com/2014/01/11/outrageously-useful-tips- maîtriser votre shell z /

Henri
la source
0

Mac OSX ne prend pas en charge la taccommande. La solution de @tcdyl fonctionne lorsque vous appelez une seule commande dans une forboucle. Pour tous les autres cas, ce qui suit est le moyen le plus simple de le contourner.

Cette approche ne prend pas en charge la présence de nouvelles lignes dans vos noms de fichiers. La raison sous-jacente est que tail -rson entrée est délimitée par des retours à la ligne.

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

Cependant, il existe un moyen de contourner la limitation de la nouvelle ligne. Si vous savez que vos noms de fichiers ne contiennent pas un certain caractère (par exemple, '='), vous pouvez utiliser trpour remplacer toutes les nouvelles lignes pour devenir ce caractère, puis effectuer le tri. Le résultat se présenterait comme suit:

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

Remarque: en fonction de votre version de tr, il peut ne pas être pris en charge en '\0'tant que personnage. Cela peut généralement être résolu en changeant les paramètres régionaux en C (mais je ne me souviens pas exactement, car après l'avoir réparé une fois, cela fonctionne maintenant sur mon ordinateur). Si vous obtenez un message d'erreur et que vous ne trouvez pas la solution de contournement, veuillez la poster en tant que commentaire, afin que je puisse vous aider à le résoudre.

Alex
la source
0

un moyen simple est d'utiliser ls -rcomme mentionné par @David Schwartz

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

Essaye ça:

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

Je pense que c'est le moyen le plus simple.

Syhra
la source
3
La question demandait « forboucle dans l'ordre inverse».
manatwork