¿Sintaxis sh para manejar cero archivos que coincidan con un comodín, y más?

¿Sintaxis sh para manejar cero archivos que coincidan con un comodín, y más?

Quiero escribir un /bin/shscript de shell que maneje cualquier archivo que coincida con un comodín. Es fácil manejar 1 o más archivos coincidentes. Sin embargo, me resulta incómodo manejar el caso de 0 archivos coincidentes.

La construcción obvia es:

#!/bin/sh
for f in *.ext; do
  handle "$f"
done

donde *.extpodría haber una o más expresiones que el shell compara con rutas de archivos, y handlees un comando de shell que se ejecuta correctamente si se le proporciona una ruta a un archivo existente, pero falla si se proporciona una ruta que no se asigna a un archivo. (En el caso que provoque esta pregunta, son *.flacy ffmpeg, pero creo que eso no importa).

Si hay archivos coincidentes foo.ext, bar.extentonces este script realiza

handle "foo.ext"
handle "bar.ext"

como se esperaba. Sin embargo, si haynocualquier archivo coincidente, este script muestra un mensaje de error como,

handle: *.ext: No such file or directory

Creo que entiendo por qué sucede esto: elintentopágina de manual(que supongo que /bin/shtambién es válido) dice que "la lista de palabras siguientes inse expande, generando una lista de elementos... Si la expansión de los elementos siguientes da como resultado una lista vacía, no se ejecuta ningún comando y el estado de retorno es 0." Aparentemente cuando *.ext"se expande", la lista de resultados incluye *.ext, en lugar de una lista vacía. Pero eso no explica cómo evitar que esto suceda.

(Actualización: hay unsh (1)página de manual del Proyecto Heirloomque describe el Bourne Shell sh, no el posterior Bourne Again Shell bash. BajoGeneración de nombre de archivo, dice claramente: "Si no se encuentra ningún nombre de archivo que coincida con el patrón, la palabra no se modifica". Explica por qué shdeja un *.extpatrón en la lista).

¿Cuál es una forma compacta e idiomática de escribir este bucle para que se ejecute cero veces sin errores si no hay archivos coincidentes, pero se ejecute una vez para cada archivo coincidente? Mejor aún, ¿qué funcionará con múltiples patrones como:

for f in *.ext1 special.ext1 *.ext2; do ...

Prefiero usar una sintaxis compatible con /bin/sh, para mayor portabilidad. Resulta que estoy usando Mac OS X 10.11.6, pero espero una sintaxis que funcione en cualquier sistema operativo tipo Unix.

Se me ocurrieron un par de formas torpes y no idiomáticas de escribir ese bucle. Si no obtengo buenas respuestas de inmediato, las contribuiré como respuestas, para que conste.

Respuesta1

La forma portátil más sencilla de hacer esto es omitir el ciclo si la expansión no produce algo que realmente exista:

for f in *.ext1 *.ext2; do
  [ -e "$f" ] || continue
  handle "$f"
done

Explicación: supongamos que hay varios archivos .ext2, pero ningún archivo .ext1. En ese caso, los comodines se expandirán a *.ext1 filea.ext2 fileb.ext2 filec.ext2. Entonces [ -e "*.ext1" ]falla y se ejecuta continue, lo que omite el resto de la iteración del ciclo. Luego [ -e "filea.ext2" ], etc. tienen éxito y esas iteraciones del bucle se ejecutan normalmente.

Por cierto, puede modificar esto, por ejemplo, [ -f "$f" ] || continuepara omitir cualquier cosa que no sea un archivo simple (si desea omitir directorios, etc.).

Por supuesto, si está ejecutando bash, puede ejecutar shopt -s nullglobprimero para no dejar patrones no coincidentes en la lista. Y shopt -u nullglobluego, para evitar efectos secundarios inesperados (por ejemplo, grep pattern *.nonexistentintentará leer desde la entrada estándar si nullglobestá configurada).

Respuesta2

Cambie a Bourne Again Shell ( /bin/bash) desde Bourne Shell ( /bin/sh) y será posible encontrar una solución sencilla.

Elbash(1)página de manualmenciona elglobo nuloopción:

Si está configurado, bash permite patrones que no coinciden con ningún archivo (consulteExpansión de nombre de rutaarriba) para expandirse a una cadena nula, en lugar de ellos mismos.

ElExpansión de nombre de rutasección dice:

Después de dividir palabras,… bash escanea cada palabra en busca de caracteres*,?, y[. Si aparece uno de estos caracteres, la palabra se considera un patrón y se reemplaza con una lista ordenada alfabéticamente de nombres de archivos que coinciden con el patrón. Si no se encuentran nombres de archivos coincidentes y la opción de shellglobo nulono está habilitado, la palabra no se modifica. Si elglobo nuloSe establece la opción y no se encuentran coincidencias, se elimina la palabra.…

Entonces, estableciendo elglobo nuloLa opción proporciona el comportamiento deseado para los patrones. Si ningún archivo coincide con el patrón, el bucle no se ejecuta.

#!/bin/bash
shopt -s nullglob
for f in *.ext; do
  handle "$f"
done

Pero elglobo nuloLa opción puede interferir con el comportamiento deseado de otros comandos. Por lo tanto, probablemente le resulte conveniente guardar la configuración existente deglobo nuloy restaurarlo después. Afortunadamente, el shopt -pcomando incorporado emite una salida en un formato que puede reutilizarse como entrada. Pero emite resultados para todas las opciones, así que utilícelo greppara seleccionar elobjeto nuloconfiguración. Consulte el registro a continuación (donde $indica el símbolo del sistema de bash):

$ shopt -p | grep nullglob
shopt -u nullglob
$ shopt -s nullglob
$ shopt -p | grep nullglob
shopt -s nullglob

Entonces, la sintaxis final de bash para manejar cero o más archivos que coincidan con un patrón comodín se ve así:

#!/bin/bash
SAVED_NULLGLOB=$(shopt -p | grep nullglob)
shopt -s nullglob
for f in *.ext; do
  handle "$f"
done
eval "$SAVED_NULLGLOB"

Además, la pregunta menciona múltiples patrones, como:

for f in *.ext1 special.ext1 *.ext2; do ...

Elglobo nuloLa opción no afecta una palabra en la lista que no sea un "patrón", por lo que special.ext1aún así se pasará al bucle. La única solución para esto es@GordonDavissoncontinueLa expresión de [ -e "$f" ] || continue.

Reconocimiento: ambos@Ignacio Vázquez-Abramsy@GordonDavissonaludido bash(1)y suglobo nuloopción. Gracias. Como no contribuyeron de inmediato con una respuesta con un ejemplo completamente elaborado, lo estoy haciendo yo mismo.

información relacionada