Pruebe si hay archivos que coincidan con un patrón para ejecutar un script

29

Estoy tratando de escribir una ifdeclaración para probar si hay algún archivo que coincida con cierto patrón. Si hay un archivo de texto en un directorio, debe ejecutar un script determinado.

Mi código actualmente:

if [ -f /*.txt ]; then ./script fi

Por favor da algunas ideas; Solo quiero ejecutar el script si hay un .txten el directorio.

usuario40952
fuente
3
¿Estás seguro de que se supone que "el directorio" es /? Además, te falta un punto y coma antes fi.
depquid
La solución más limpia y robusta que he encontrado es usar findcomo se explica aquí en stackoverflow .
Joshua Goldberg

Respuestas:

39
[ -f /*.txt ]

devolvería verdadero solo si hay un (y solo uno) archivo no oculto en /cuyo nombre termina en.txt y si ese archivo es un archivo normal o un enlace simbólico a un archivo normal.

Esto se debe a que el shell expande los comodines antes de pasarlos al comando (aquí [).

Así que si hay una /a.txt, y /b.txt, [será pasado 5 argumentos: [, -f, /a.txt, /b.txty ]. [luego se quejaría de que-f se le dan demasiados argumentos.

Si desea comprobar que el *.txtpatrón se expande al menos a un archivo no oculto (normal o no):

shopt -s nullglob
set -- *.txt
if [ "$#" -gt 0 ]; then
  ./script "$@" # call script with that list of files.
fi
# Or with bash arrays so you can keep the arguments:
files=( *.txt )
# apply C-style boolean on member count
(( ${#files[@]} )) && ./script "${files[@]}"

shopt -s nullglobes bashespecífico, pero como conchas ksh93, zsh, yash,tcsh tienen declaraciones equivalentes.

Tenga en cuenta que encuentra esos archivos leyendo el contenido del directorio, no intenta acceder a esos archivos en absoluto, lo que lo hace más eficiente que las soluciones que llaman comandos como lso staten esa lista de archivos calculados por el shell.

El shequivalente estándar sería:

set -- [*].txt *.txt
case "$1$2" in
  ('[*].txt*.txt') ;;
  (*) shift; script "$@"
esac

El problema es que con los shells Bourne o POSIX, si un patrón no coincide, se expande a sí mismo. Entonces, si se *.txtexpande a *.txt, no sabes si es porque no hay un .txtarchivo en el directorio o porque hay un archivo llamado *.txt. El uso [*].txt *.txtpermite discriminar entre los dos.

Stéphane Chazelas
fuente
[ -f /*.txt ]es bastante rápido en comparación con compgen.
Daniel Böhmer el
@ DanielBöhmer [ -f /*.txt ]estaría equivocado, pero en mi prueba en un directorio que contiene 3425archivos, 94de los cuales son archivos txt no ocultos, compgen -G "*.txt" > /dev/null 2>&1parecen ser tan rápidos como set -- *.txt; [ "$#" -gt 0 ](20.5 segundos para ambos cuando se repiten 10000 veces en mi caso).
Stéphane Chazelas
11

Siempre puedes usar find:

find . -maxdepth 1 -type f -name "*.txt" 2>/dev/null | grep -q . && ./script

Explicación:

  • find . : busca en el directorio actual
  • -maxdepth 1: no buscar subdirectorios
  • -type f : busca solo archivos regulares
  • name "*.txt" : busca archivos que terminan en .txt
  • 2>/dev/null : redirige los mensajes de error a /dev/null
  • | grep -q . : grep para cualquier carácter, devolverá falso si no se encuentran caracteres.
  • && ./script: Ejecutar ./scriptsolo si el comando anterior fue exitoso ( &&)
terdon
fuente
2
findsolo devuelve falso si tiene problemas para buscar archivos, no si no encuentra ningún archivo. Desea canalizar la salida grep -q .para verificar si encuentra algo.
Stéphane Chazelas
@StephaneChazelas tienes toda la razón, por supuesto. Aunque extraño, lo había probado y parecía funcionar. Debe haber hecho algo extraño porque ya no lo hace. ¿Cuándo encontrará "tiene problemas para encontrar archivos"?
terdon
@terdon, como cuando algún directorio es inaccesible, o errores de E / S o cualquier error devuelto por cualquier llamada al sistema que realice. En ese caso, intente después chmod a-x ..
Stéphane Chazelas
8

Una posible solución es también Bash incorporado compgen. Ese comando devuelve todas las coincidencias posibles para un patrón global y tiene un código de salida que indica si algún archivo coincide.

compgen -G "/*.text" > /dev/null && ./script

Encontré esta pregunta mientras buscaba soluciones más rápidas.

Daniel Böhmer
fuente
1
¡Buen descubrimiento! Si está en una configuración regional de varios bytes, puede mejorarlo ligeramente con LC_ALL=C compgen -G "*.txt" > /dev/null.
Stéphane Chazelas
7

Aquí hay un trazador de líneas para hacerlo:

$ ls
file1.pl  file2.pl

existen archivos

$ stat -t *.pl >/dev/null 2>&1 && echo "file exists" || echo "file doesn't exist"
file exists

los archivos no existen

$ stat -t -- *.txt >/dev/null 2>&1 && echo "file exists" || echo "file don't exist"
file don't exist

Este enfoque hace uso de ||y&& operadores en bash. Estos son los operadores "o" y "y".

Entonces, si el comando stat devuelve un $?igual a 0, entonces echose llama al primero , si devuelve un 1, entonces el segundoecho se llama .

devolver resultados de stat

# a failure
$ stat -t -- *.txt >/dev/null 2>&1
$ echo "$?"
1

# a success
$ stat -t -- *.pl >/dev/null 2>&1
$ echo "$?"
0

Esta pregunta está ampliamente cubierta en stackoverflow:

slm
fuente
1
¿Por qué usar el no estándar statcuando ls -dpuede hacer lo mismo?
Stéphane Chazelas
Pensé que ls -denumera un directorio? No pareció funcionar cuando intenté enumerar un directorio con archivos, ls -d *.plpor ejemplo.
slm
Puede reemplazar la declaración a la izquierda de &&by ls *.txty funcionará también. Asegúrese de enviar el stdout y stderr a /dev/nulllo sugerido por @slm.
unxnut
1
Si utiliza ls *.txty no hay archivos presentes en el directorio de este devolverá una $? = 2, que seguirá funcionando con el caso entonces, pero esta fue una de mis razones para elegir statmás ls. Quería un 0 para el éxito y un 1 para el fracaso.
slm
ls -des enumerar directorios en lugar de su contenido. Así que ls -dsolo hace lo lstaten el archivo, tal como lo stathace GNU . Lo que los comandos de estado de salida distintos de cero devuelven en caso de falla es específico del sistema, tiene poco sentido hacer suposiciones sobre ellos.
Stéphane Chazelas
4

Como señala Chazelas, su script fallaría si la expansión de comodines coincide con más de un archivo.

Sin embargo, hay un truco que uso ( aunque no me gusta mucho ) para evitarlo:

PATTERN=(/*.txt)
if [ -f ${PATTERN[0]} ]; then
...
fi

¿Cómo funciona?

La expansión de comodines coincidirá con una serie de nombres de archivos, obtenemos el primero si hay alguno, de lo contrario, nulo si no coincide.

Señor pei
fuente
OMI, esta es la respuesta menos mala aquí. Sin embargo, todos parecen bastante horribles, ya que falta una característica básica del lenguaje.
plugwash
@plugwash es intencional ... * los scripts de shell de nix tienen un control de flujo básico y algunas otras probabilidades y termina, pero al final del día su trabajo es unir otros comandos. Si bash apesta ... es porque los comandos que usas de él apestan
cb88
2
Esa es la lógica incorrecta (y te faltan comillas). Eso comprueba si el primer archivo coincidente es un archivo normal. Bien puede ser un archivo no regular, pero puede haber varios otros .txtarchivos que son de tipo regular . Prueba por ejemplo después mkdir a.txt; mkfifo b.txt; echo regular > c.txt.
Stéphane Chazelas
1

Simple como:

cnt=`ls \*.txt 2>/dev/null | wc -l`
if [ "$cnt" != "0" ]; then ./script fi

wc -l cuenta las líneas en el comodín expandido.

Kim Johansson
fuente
1
Voto a favor: recoge una increíble cantidad de antipatrones para principiantes en una pequeña cantidad de código. No debe analizar la lssalida y casi nunca examinarla $?directamente porque ifya lo hace. Además, usar wcpara ver si sucedió algo está igualmente mal dirigido.
tripleee
0

Me gusta la solución de matriz anterior, pero eso podría ser un desperdicio con una gran cantidad de archivos: el shell usaría una gran cantidad de memoria para construir la matriz, y solo se probaría el primer elemento.

Aquí hay una estructura alternativa que probé ayer:

$ cd /etc; if [[ $(echo * | grep passwd) ]];then echo yes;else echo no;fi yes $ cd /etc; if [[ $(echo * | grep password) ]];then echo yes;else echo no;fi no

El valor de salida del grep parece estar determinando la ruta a través de la estructura de control. Esto también se prueba con expresiones regulares en lugar de patrones de shell. Algunos de mis sistemas tienen el comando "pcregrep" que permite coincidencias de expresiones regulares mucho más sofisticadas.

(Edité esta respuesta para eliminar un "ls" en la sustitución del comando después de leer la crítica anterior por analizarlo).

Charlie
fuente
-2

si desea usar una cláusula if, evalúe el recuento:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
Oxidado75
fuente