¿Cómo invierto un bucle for?


26

¿Cómo hago un bucle correctamentefor en orden inverso?

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

Necesito una solución que no se rompa para los personajes originales en los nombres de archivo.


55
Simplemente dirígete a sort -rantes de for, o lávate ls -r.
David Schwartz

Respuestas:


27

En bash o ksh, coloque los nombres de archivo en una matriz e itere sobre esa matriz en orden inverso.

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

El código anterior también funciona en zsh si la ksh_arraysopción está configurada (está en modo de emulación ksh). Hay un método más simple en zsh, que es invertir el orden de las coincidencias a través de un calificador global:

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

POSIX no incluye matrices, por lo que si desea ser portátil, su única opción para almacenar directamente una matriz de cadenas son los parámetros posicionales.

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

Una vez que esté utilizando parámetros posicionales, no hay necesidad de sus variables iy f; simplemente realice un shift, use $1para su llamada bary pruebe [ -z $1 ]en su while.
user1404316

@ user1404316 Esta sería una forma demasiado complicada de iterar sobre los elementos en orden ascendente . Además, incorrecto: ni [ -z $1 ]tampoco [ -z "$1" ]son pruebas útiles (¿qué pasa si el parámetro era *, o una cadena vacía?). Y en cualquier caso, no ayudan aquí: la pregunta es cómo hacer un bucle en el orden opuesto.
Gilles 'SO- deja de ser malvado'

La pregunta específicamente dice que está iterando sobre los nombres de archivo en / var / log, por lo que es seguro asumir que ninguno de los parámetros sería "*" o vacío. Sin embargo, estoy corregido en el punto del orden inverso.
user1404316

7

Intente esto, a menos que considere los saltos de línea como "personajes originales":

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

¿Hay algún método que funcione con saltos de línea? (Sé que es raro, pero al menos por el bien del aprendizaje me gustaría saber si es posible escribir el código correcto.)
Mehrdad

Creativo para usar tac para revertir el flujo, y si desea deshacerse de algunos caracteres no deseados como saltos de línea, puede canalizar a tr -d '\ n'.
Johan

44
Esto se rompe si los nombres de archivo contienen nuevas líneas, barras diagonales inversas o caracteres no imprimibles. Consulte mywiki.wooledge.org/ParsingLs y ¿Cómo recorrer las líneas de un archivo? (y los hilos vinculados).
Gilles 'SO- deja de ser malvado'

La respuesta más votada rompió la variable fen mi situación. Sin embargo, esta respuesta actúa más o menos como un reemplazo directo para la forlínea ordinaria .
Serge Stroobandt

2

Si alguien está tratando de descubrir cómo revertir la iteración sobre una lista de cadenas delimitadas por espacios, esto funciona:

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

list="a bb ccc"

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

2
No tengo tac en mi sistema; No sé si es robusto, pero he estado usando para x en $ {mylist}; do revv = "$ {x} $ {revv}"; hecho
arp

@arp Eso es algo impactante ya que taces parte de los coreutils de GNU. Pero su solución también es buena.
ACK_stoverflow

@ACK_stoverflow Existen sistemas sin herramientas de usuario de Linux.
Kusalananda

@Kusalananda ¿Estás diciendo que solo Linux usa GNU coreutils? Jajaja Incluso busybox tiene tac. Aunque por la cantidad de tacrespuestas sin respuesta aquí, supongo que quizás OSX no tiene tac. En cuyo caso, use alguna variante de la solución de @ arp; de todos modos, es más fácil de entender.
ACK_stoverflow

@ACK_stoverflow Eso es lo que estoy diciendo, sí.
Kusalananda

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

Debe funcionar de la manera que desee (esto se probó en Mac OS X y tengo una advertencia a continuación ...).

Desde la página del 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).

Básicamente, está buscando los archivos que coinciden con su cadena + glob y termina cada uno con un carácter NUL. Si sus nombres de archivo contienen nuevas líneas u otros caracteres extraños, find debería manejar esto bien.

tail -r

toma la entrada estándar a través de la tubería y la invierte (tenga en cuenta que tail -rimprime toda la entrada en stdout, y no solo las últimas 10 líneas, que es el valor predeterminado estándar. man tailpara obtener más información).

Luego lo canalizamos a 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).

Aquí, xargs espera ver argumentos separados por el carácter NUL, desde el cual pasó findy revirtió tail.

Mi advertencia: he leído que tailno funciona bien con cadenas terminadas en nulo . Esto funcionó bien en Mac OS X, pero no puedo garantizar que ese sea el caso para todos los * nixes. Ve con cuidado.

También debo mencionar que GNU Parallel se usa a menudo como una xargsalternativa. También puedes comprobar eso.

Puede que me falte algo, por lo que otros deberían intervenir.


2
+1 gran respuesta. Sin embargo, parece que Ubuntu no es compatible tail -r... ¿Estoy haciendo algo mal?
Mehrdad

1
No, no creo que lo seas. No tengo mi máquina Linux activada, pero un google rápido para 'linux tail man' no lo muestra como una opción. Sunaku mencionó taccomo una alternativa, así que lo intentaría, en su lugar
tcdyl

También he editado la respuesta para incluir otra alternativa
tcdyl

whoops Olvidé hacer +1 cuando dije +1 :( ¡Listo! Lo siento por eso jaja :)
Mehrdad

44
tail -res específico de OSX e invierte la entrada delimitada por nueva línea, no la entrada delimitada por nulos. Su segunda solución no funciona en absoluto (está canalizando información ls, lo que no le importa); No hay una solución fácil que lo haga funcionar de manera confiable.
Gilles 'SO- deja de ser malvado'

1

En su ejemplo, está recorriendo varios archivos, pero encontré esta pregunta debido a su título más general, que también podría abarcar la reproducción en bucle sobre una matriz o la inversión según cualquier número de órdenes.

Aquí se explica cómo hacerlo en Zsh:

Si está recorriendo los elementos en una matriz, use esta sintaxis ( fuente )

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

Oinvierte el orden especificado en el siguiente indicador; aes el orden normal de la matriz.

Como dijo @Gilles, Onrevertirá el orden de sus archivos globales, por ejemplo, con my/file/glob/*(On). Eso es porque Ones " orden de nombre inverso ".

Banderas de clasificación Zsh:

  • a orden de la matriz
  • L longitud del archivo
  • l número de enlaces
  • m fecha de modificación
  • n nombre
  • ^oorden inverso ( oes el orden normal)
  • O orden inverso

Para ver ejemplos, consulte https://github.com/grml/zsh-lovers/blob/master/zsh-lovers.1.txt y http://reasoniamhere.com/2014/01/11/outrageously-useful-tips- para-dominar-tu-z-shell /


0

Mac OSX no es compatible con el taccomando. La solución de @tcdyl funciona cuando se llama a un solo comando en un forbucle. Para todos los demás casos, la siguiente es la forma más sencilla de evitarlo.

Este enfoque no admite tener nuevas líneas en sus nombres de archivo. La razón subyacente es que tail -rordena su entrada delimitada por nuevas líneas.

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

Sin embargo, hay una manera de evitar la limitación de la nueva línea. Si sabe que sus nombres de archivo no contienen un determinado carácter (por ejemplo, '='), puede usar trpara reemplazar todas las líneas nuevas para convertirse en este carácter y luego ordenarlas. El resultado sería el siguiente:

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: dependiendo de su versión de tr, es posible que no sea compatible '\0'con un personaje. Esto generalmente se puede solucionar cambiando la configuración regional a C (pero no recuerdo exactamente cómo, ya que después de arreglarlo una vez ahora funciona en mi computadora). Si recibe un mensaje de error y no puede encontrar la solución, publíquelo como comentario para que pueda ayudarlo a solucionarlo.


0

una manera fácil es usar ls -rcomo lo menciona @David Schwartz

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

-4

Prueba esto:

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

Creo que es la forma más simple.


3
La pregunta pedía " forbucle en orden inverso".
manatwork
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.