¿Cómo pasar una expresión regular al encontrar una ruta de directorio en bash?

14

He escrito un pequeño script bash para encontrar si un directorio llamado anacondao minicondaen mi usuario $HOME. Pero no encuentra el miniconda2directorio en mi casa.

¿Cómo podría solucionar esto?

if [ -d "$HOME"/"(ana|mini)conda[0-9]?" ]; then
    echo "miniconda directory is found in your $HOME"
else
    echo "anaconda/miniconda is not found in your $HOME"
fi

PD: si lo tengo [ -d "$HOME"/miniconda2 ]; then, entonces encuentra el directorio miniconda2, así que creo que el error radica en la parte"(ana|mini)conda[0-9]?"

Quiero que el guión sea general. Para mí, es miniconda2, pero para algún otro usuario podría ser anaconda2, miniconda3, etc.

Jenny
fuente
Otro usuario podría usar anaconda_2 o -2 o -may2019. Entonces, ¿xxxconda * no sería mejor?
WinEunuuchs2Unix
2
La expansión del nombre de archivo Bash usa expresiones glob, no expresiones regulares.
Peter Cordes

Respuestas:

13

Esto es algo sorprendentemente complicado de hacer bien.

Básicamente, -dsolo probará un único argumento, incluso si puede hacer coincidir los nombres de archivo con una expresión regular.

Una forma sería darle la vuelta al problema y probar directorios para una coincidencia de expresiones regulares en lugar de probar la coincidencia de expresiones regulares para directorios. En otras palabras, recorra todos los directorios $HOMEcon un simple glob de shell y pruebe cada uno contra su expresión regular, rompiendo una coincidencia y finalmente probando si la BASH_REMATCHmatriz no está vacía:

#!/bin/bash

for d in "$HOME"/*/; do
  if [[ $d =~ (ana|mini)conda[0-9]? ]]; then
    break;
  fi
done

if ((${#BASH_REMATCH[@]} > 0)); then
    echo "anaconda/miniconda directory is found in your $HOME"
  else
    echo "anaconda/miniconda is not found in your $HOME"
fi

Una forma alternativa sería utilizar un globo de caparazón extendido en lugar de la expresión regular y capturar cualquier coincidencia de globo en una matriz. Luego pruebe si la matriz no está vacía:

#!/bin/bash

shopt -s extglob nullglob

dirs=( "$HOME"/@(ana|mini)conda?([0-9])/ )

if (( ${#dirs[@]} > 0 )); then
  echo "anaconda/miniconda directory is found in your $HOME"
else
  echo "anaconda/miniconda is not found in your $HOME"
fi

El seguimiento /garantiza que solo los directorios coincidan; la nullglobevita que la cáscara de regresar la cadena sin igual en el caso de los partidos cero.


Para hacer que sea recursivo, configure la globstaropción de shell ( shopt -s globstar) y luego respectivamente: -

  • (versión regex): for d in "$HOME"/**/; do

  • (versión glob extendida): dirs=( "$HOME"/**/@(ana|mini)conda?([0-9])/ )

conductor de acero
fuente
1
Yo iría por la ruta de la matriz. Puede usar ?([0-9])en lugar de @(|[0-9]): ?(...)coincide con cero o uno, igual que el ?cuantificador de expresiones regulares .
Glenn Jackman
2
Ni siquiera necesita extglob si usa la expansión de llaves (esto genera todos los nombres coincidentes posibles):~/{ana,mini}conda{0..9}*/
xenoid
¿Hay alguna forma de editar cualquiera de estas soluciones por lo que llevará a cabo incluso si minio anacondase instala en $HOME/sub-directories? Por ejemplo$HOME/sub-dir1/sub-dir2/miniconda2
Jenny
1
@Jenny por favor ver mi edición relativoglobstar
steeldriver
1
@terdon, sí, realmente no quería ir por la madriguera de lo que es lo "correcto" para combinar: simplemente tomé la expresión regular del OP con el fin de ilustrar un enfoque general
Steeldriver
9

De hecho, como ya se mencionó, esto es complicado. Mi enfoque es el siguiente:

  • use findy sus capacidades de expresiones regulares para encontrar los directorios en cuestión.
  • deje findimprimir un xpara cada directorio encontrado
  • almacenar los xes en una cadena
  • si la cadena no está vacía, se encontró uno de los directorios.

Así:

xString=$(find $HOME -maxdepth 1 \
                     -type d \
                     -regextype egrep \
                     -regex "$HOME/(ana|mini)conda[0-9]?" \
                     -printf 'x');
if [ -n "$xString" ]; then
    echo "found one of the directories";
else
    echo "no match.";
fi

Explicación:

  • find $HOME -maxdepth 1encuentra todo a continuación, $HOME pero restringe la búsqueda a un nivel (es decir, no se repite en subdirectorios).
  • -type drestringe la búsqueda a solo directorios
  • -regextype egrepdice con findqué tipo de expresión regular tratamos. Esto es necesario porque cosas como [0-9]?y (…|…)son algo especiales y find no las reconoce por defecto.
  • -regex "$HOME/(ana|mini)conda[0-9]?"es la expresión regular real que queremos buscar
  • -printf 'x'solo imprime un xpor cada cosa que satisfaga las condiciones anteriores.
PerlDuck
fuente
Cuando hay un partido. -bash: -regex: command not found found one of the directories
Jenny
Hola PerlDuck: gracias. Una buena respuesta también. Pero recibo un error. printfPor ejemplo, cuando ejecuto el script, funciona bien, pero no encuentra el comando printf cuando no hay coincidencia, pero creo que es porque no hay nada que imprimir. -bash: -printf: command not found no match.
Jenny
3
@ Jenny Es posible que haya cometido un error tipográfico al copiarlo, ya que funciona bien para mí. -printfno es un comando sino un argumento para find. Eso es lo que hace la barra invertida al final de la línea anterior.
wjandrea
1
Sugeriría -quitdespués de imprimir la ruta encontrada, a menos que quiera seguir detectando ambigüedad.
Peter Cordes
¿Y por qué no imprimir la ruta real? Ya lo tiene, por lo que parece una pena descartarlo y usarlo xen su lugar:foundDir=$(find $HOME -maxdepth 1 -type d -regextype egrep -regex "$HOME/(ana|mini)conda[0-9]?" -print -quit); echo "found $foundDir"
terdon
2

Puede recorrer una lista de nombres de directorio que desea probar y actuar si existe uno de ellos:

a=0
for i in {ana,mini}conda{,2}; do
  if [ -d "$i" ]; then
    unset a
    break
  fi
done
echo "anaconda/miniconda directory is ${a+not }found in your $HOME"

Obviamente, esta solución no permite la potencia de expresión regular completa, pero la expansión del refuerzo de caparazón y la abrazadera es igual al menos en el caso que usted mostró. El bucle sale tan pronto como existe un directorio y desarma la variable establecida previamente a. En la echolínea siguiente , la expansión del parámetro se${a+not } expande a nada si ase establece (= no se encuentra el directorio) y "no" más.

postre
fuente
1

Posible solución es buscar miniconda y anaconda por separado como se muestra a continuación

if [ -d "$HOME"/miniconda* ] || [ -d "$HOME"/anaconda* ]; then
    echo "miniconda directory is found in your $HOME"
else
    echo "anaconda/miniconda is not found in your $HOME"
fi

Pero si alguien tiene sugerencias, me gustaría saber por qué no podemos pasar una expresión regular cuando buscamos directorios.

Jenny
fuente
2
He votado a favor, pero luego me di cuenta de que se romperá si el usuario tiene más de un directorio coincidente (p. Ej., Miniconda Y miniconda2)
steeldriver
@steeldriver: "se romperá si el usuario tiene más de un directorio coincidente" Sí, eso es cierto. ¿Tienes alguna sugerencia de cómo solucionarlo?
Jenny
@ Jenny Usa una matriz, como en la respuesta de steeldriver. shopt -s nullglob; dirs=( "$HOME"/miniconda* "$HOME"/anaconda* ); if (( ${#dirs[@]} > 0 )); then ...
wjandrea
Si se reemplaza ] || [con el -oque al menos no debería romperse si se encuentran ambos directorios ya que ambos globos de directorio se buscan en la misma prueba.
Phoenix
@steeldriver y Jenny: es posible que desee romper la ambigüedad en lugar de solo elegir una. Haga que el usuario especifique su directorio en lugar de elegir el incorrecto. (por ejemplo, edite el script para establecer el nombre del directorio en lugar de ejecutar el código de autodetección).
Peter Cordes