¿Cómo pasar el comodín '*' al parámetro de ruta del comando find a través de una variable en el script?

9

Quiero usar findpara buscar archivos en un conjunto de carpetas restringido por comodines, pero donde hay espacios en el nombre de la ruta.

Desde la línea de comandos, esto es fácil. Los siguientes ejemplos funcionan todos.

find  te*/my\ files/more   -print
find  te*/'my files'/more  -print
find  te*/my' 'files/more  -print

Estos encontrarán archivos en, por ejemplo, terminal/my files/morey tepid/my files/more.

Sin embargo, necesito que esto sea parte de un script; lo que necesito es algo como esto:

SEARCH='te*/my\ files/more'
find ${SEARCH} -print

Desafortunadamente, haga lo que haga, parece que no puedo mezclar comodines y espacios en un findcomando dentro de un script. El ejemplo anterior devuelve los siguientes errores (observe la duplicación inesperada de la barra invertida):

find: te*/my\\’: No such file or directory
find: files/more’: No such file or directory

Intentar usar comillas también falla.

SEARCH="te*/'my files'/more"
find ${SEARCH} -print

Esto devuelve los siguientes errores, habiendo ignorado el significado de las citas:

find: te*/'my’: No such file or directory
find: ‘files'/more’: No such file or directory

Aquí hay un ejemplo más.

SEARCH='te*/my files/more'
find ${SEARCH} -print

Como se esperaba:

find: te*/my’: No such file or directory
find: files/more’: No such file or directory

Cada variación que he intentado devuelve un error.

Tengo una solución alternativa, que es potencialmente peligrosa porque devuelve demasiadas carpetas. Convierto todos los espacios en un signo de interrogación (comodín de un solo carácter) como este:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /?}       # Convert every space to a question mark.
find ${SEARCH} -print

Este es el equivalente de:

find te*/my?files/more -print

Esto devuelve no solo las carpetas correctas, sino también terse/myxfiles/more, lo que no se supone que debe hacer.

¿Cómo puedo lograr lo que intento hacer? Google no me ha ayudado :(

Paddy Landau
fuente
@KasiyA Estoy usando bash; debes estar usando algo más, ya que no he visto esa construcción antes. El comando da como resultado SEARCH: command not foundla find -printejecución del comando .
Paddy Landau
Un tiro en la oscuridad, pero ¿qué pasa con las citas? find "${SEARCH}" -print?
Alaa Ali
@AlaaAli No, no funciona, porque las citas impiden que Bash use los comodines. Buscará una ruta específicamente con el nombre (en mi ejemplo) te*/'my files'/more.
Paddy Landau

Respuestas:

9

El mismo comando exactamente debería funcionar bien en un script:

#!/usr/bin/env bash
find  te*/my\ files/ -print

Si necesita tenerlo como variable, se vuelve un poco más complejo:

#!/usr/bin/env bash
search='te*/my\ files/'
eval find "$search" -print

ADVERTENCIA:

Usarlo de evalesa manera no es seguro y puede resultar en la ejecución de código arbitrario y posiblemente dañino si los nombres de sus archivos pueden contener ciertos caracteres. Consulte las preguntas frecuentes 48 de bash para obtener más información.

Es mejor pasar el camino como argumento:

#!/usr/bin/env bash
find "$@" -name "file*"

Otro enfoque es evitar por findcompleto y utilizar las funciones de globing y globs extendidos de bash:

#!/usr/bin/env bash
shopt -s globstar
for file in te*/my\ files/**; do echo "$file"; done

La globstaropción bash te permite usar **para hacer coincidir recursivamente:

globstar
      If set, the pattern ** used in a pathname expansion con
      text will match all files and zero or  more  directories
      and  subdirectories.  If the pattern is followed by a /,
      only directories and subdirectories match.

Para que funcione al 100% como buscar e incluir archivos de puntos (archivos ocultos), use

#!/usr/bin/env bash
shopt -s globstar
shopt -s dotglob
for file in te*/my\ files/**; do echo "$file"; done

Puedes incluso echoigualarlos directamente sin el bucle:

echo te*/my\ files/**
terdon
fuente
2
He establecido esto como la respuesta debido a los útiles comentarios que ha hecho terdon (sin olvidar los útiles comentarios de otros). Utilicé la capacidad de Bash en la línea de comando para pasar múltiples rutas a mi script, en lugar de que el script intentara solucionarlo. Funciona bien.
Paddy Landau
2

¿Qué hay de las matrices?

$ tree Desktop/ Documents/
Desktop/
└── my folder
    └── more
        └── file
Documents/
└── my folder
    ├── folder
    └── more

5 directories, 1 file
$ SEARCH=(D*/my\ folder)
$ find "${SEARCH[@]}" 
Desktop/my folder
Desktop/my folder/more
Desktop/my folder/more/file
Documents/my folder
Documents/my folder/more
Documents/my folder/folder

(*)se expande en una matriz de lo que coincida con el comodín. Y se "${SEARCH[@]}"expande en todos los elementos de la matriz ( [@]), con cada uno citado individualmente.

Tardíamente, me doy cuenta de que debe ser capaz de hacer esto. Algo como:

find . -path 'D*/my folder/more/'
muru
fuente
Idea inteligente, pero por desgracia, no funciona. ¿Por qué? Porque el camino en sí se mantiene en una variable; por lo tanto, INPUTPATH='te*/my files/morey SEARCH=(${INPUTPATH}). No importa cómo varíe la forma en que hago esto, todavía termino con un resultado no funcional. Esto parece imposible!
Paddy Landau
Todo esto es cierto, por supuesto, pero el OP debe hacerlo en un script. Eso cambia las cosas ya que la expansión de los comodines se vuelve significativamente más compleja y esto no funciona.
terdon
@PaddyLandau En cuyo caso, ¿por qué no puedes usar findel -pathfiltro? Utiliza comodines y definitivamente no necesita expansión.
muru
@muru Eso es interesante; No sabía acerca -path. En los últimos 10 minutos, sin embargo, he descubierto la respuesta: eval¡ uso ! Parece ser más simple que -path.
Paddy Landau
2
@terdon y muru y todos: Gracias. He escuchado lo que todos han dicho, y me di cuenta de que debería hacer que mi script haga una cosa y dejar que Bash globbing pase múltiples archivos o rutas al script. He modificado mi script así. Funciona bien y encaja mejor con la filosofía de Linux. ¡Gracias de nuevo!
Paddy Landau
0

Finalmente he encontrado la respuesta.

Agregue una barra diagonal inversa a todos los espacios:

SEARCH='te*/my files/more'
SEARCH=${SEARCH// /\\ }

En este punto, SEARCHcontiene te*/my\ files/more.

Entonces, use eval.

eval find ${SEARCH} -print

¡Es así de simple! El uso evalevita la interpretación que ${SEARCH}proviene de una variable.

Paddy Landau
fuente
@terdon Gracias por la advertencia. De vuelta a la mesa de dibujo!
Paddy Landau
Sí, esto es sorprendentemente complicado. Acabo de actualizar mi respuesta con otro enfoque, ¿por qué no usar globbing en su lugar? Si eso aún no funciona, le sugiero que publique una nueva pregunta en Unix y Linux explicando cuál es su objetivo final y por qué necesita tener el patrón como variable. Este tipo de cosas es más probable que obtenga una mejor respuesta allí.
terdon