Como passar um regex ao encontrar um caminho de diretório no bash?

14

Eu escrevi um pequeno script bash para descobrir se um diretório nomeado anacondaou minicondano meu usuário $HOME. Mas ele não encontra o miniconda2diretório em minha casa.

Como eu poderia consertar isso?

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

PS: Se eu tiver [ -d "$HOME"/miniconda2 ]; then, ele encontra o diretório miniconda2, então acho que o erro está na parte"(ana|mini)conda[0-9]?"

Eu quero que o script seja geral. Para mim, é miniconda2, mas para outros usuários, pode ser anaconda2, miniconda3 e assim por diante.

Jenny
fonte
Outro usuário pode usar anaconda_2 ou -2 ou -may2019. Então xxxconda * não seria melhor?
WinEunuuchs2Unix 31/05/19
2
A expansão do nome de arquivo Bash usa expressões glob, não expressões regulares.
Peter Cordes

Respostas:

13

É uma coisa surpreendentemente complicada de se fazer bem.

Fundamentalmente, -dtestará apenas um único argumento - mesmo se você puder corresponder os nomes de arquivos usando uma expressão regular.

Uma maneira seria inverter o problema e testar os diretórios para uma correspondência de regex em vez de testar a correspondência de regex para diretórios. Em outras palavras, faça um loop sobre todos os diretórios $HOMEusando um simples shell glob e teste cada um deles em relação ao seu regex, quebrando uma correspondência e finalmente testando se a BASH_REMATCHmatriz não está vazia:

#!/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

Uma maneira alternativa seria usar um globo de shell estendido no lugar do regex e capturar quaisquer correspondências de globos em uma matriz. Em seguida, teste se a matriz não está vazia:

#!/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

O final /garante que apenas os diretórios sejam correspondidos; o nullglobevita que o shell de retornar a string inigualável no caso de zero partidas.


Para tornar recursivo, defina a globstaropção shell ( shopt -s globstar) e, em seguida, respectivamente: -

  • (versão regex): for d in "$HOME"/**/; do

  • (versão estendida glob): dirs=( "$HOME"/**/@(ana|mini)conda?([0-9])/ )

chave de aço
fonte
1
Eu seguiria a rota do array. Você pode usar ?([0-9])no lugar de @(|[0-9])- ?(...)corresponde a zero ou um, igual ao ?quantificador de expressão regular.
Glenn Jackman
2
Extglob Você não precisa mesmo é você usar a expansão cinta (isto gera todos os nomes correspondentes possíveis):~/{ana,mini}conda{0..9}*/
xenoid
Existe uma maneira de editar qualquer uma destas soluções para que ele irá realizar até mesmo se miniou anacondaestá instalado em $HOME/sub-directories? Por exemplo$HOME/sub-dir1/sub-dir2/miniconda2
Jenny
1
@Jenny consulte a minha edição respeitoglobstar
steeldriver
1
@terdon sim, eu realmente não quero ir no buraco do coelho do que é a coisa "certa" para corresponder - Eu apenas tomei regex do OP como está com a finalidade de ilustrar uma abordagem geral
steeldriver
9

De fato, como já mencionado, isso é complicado. Minha abordagem é a seguinte:

  • use finde seus recursos regex para encontrar os diretórios em questão.
  • vamos findimprimir um xpara cada diretório encontrado
  • armazene os xes em uma string
  • se a sequência não estiver vazia, um dos diretórios foi encontrado.

Portanto:

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

Explicação:

  • find $HOME -maxdepth 1localiza tudo abaixo, $HOME mas restringe a pesquisa a um nível (ou seja: não recursa em subdiretórios).
  • -type drestringe a pesquisa apenas a directorias
  • -regextype egrepdiz findque tipo de expressão regular lidamos. Isso é necessário porque coisas como [0-9]?e (…|…)são um pouco especiais e find não as reconhecem por padrão.
  • -regex "$HOME/(ana|mini)conda[0-9]?"é a expressão regular real que queremos procurar
  • -printf 'x'apenas imprime um xpara cada coisa que satisfaça as condições anteriores.
PerlDuck
fonte
Quando houver uma correspondência. -bash: -regex: command not found found one of the directories
Jenny
Oi PerlDuck: Obrigado. Uma boa resposta também. Mas eu recebo um erro. printfPor exemplo, quando executo o script, ele funciona ok, mas não encontra o comando printf quando não há correspondência, mas acho que é porque não há nada para imprimir ?. -bash: -printf: command not found no match.
Jenny
3
@ Jenny Você pode ter cometido um erro de digitação ao copiá-lo, pois funciona bem para mim. -printfnão é um comando, mas um argumento para find. É o que a barra invertida no final da linha anterior faz.
Wjandrea 31/05/19
1
Eu sugeriria -quitdepois de imprimir o caminho encontrado, a menos que você queira continuar detectando ambiguidade.
Peter Cordes
E por que não imprimir o caminho real? Você já o possui, então parece uma pena descartá-lo e usá-lo x:foundDir=$(find $HOME -maxdepth 1 -type d -regextype egrep -regex "$HOME/(ana|mini)conda[0-9]?" -print -quit); echo "found $foundDir"
terdon
2

Você pode fazer um loop sobre uma lista de nomes de diretórios que deseja testar e agir sobre ela, se existir um deles:

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, essa solução não permite a potência máxima de regex, mas a expansão de globbing e brace do shell é igual pelo menos no caso que você mostrou. O loop sai assim que um diretório existe e desativa a variável definida anteriormente a. Na echolinha subseqüente , a expansão do parâmetro se${a+not } expande para nada se aestiver definida (= nenhum diretório encontrado) e "not" em outros casos.

sobremesa
fonte
1

Possível solução alternativa é pesquisar miniconda e anaconda separadamente, como mostrado abaixo

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

Mas se alguém tiver sugestões, eu gostaria de saber por que não podemos passar um regex ao procurar por diretórios.

Jenny
fonte
2
I upvoted isso - mas então percebeu que vai quebrar se o usuário tiver mais de um diretório de correspondência (por exemplo miniconda E miniconda2)
steeldriver
@steeldriver: "será quebrado se o usuário tiver mais de um diretório correspondente" Sim, isso é verdade. Você tem alguma sugestão de como corrigi-lo?
Jenny
@ Jenny Use uma matriz, como na resposta da chave de aço. shopt -s nullglob; dirs=( "$HOME"/miniconda* "$HOME"/anaconda* ); if (( ${#dirs[@]} > 0 )); then ...
Wjandrea 31/05/19
Se você substituí ] || [- -olo, pelo menos não deve quebrar se os dois diretórios forem encontrados, pois os dois globs de diretório são procurados no mesmo teste.
Phoenix
@steeldriver e Jenny: você pode querer quebrar a ambiguidade em vez de apenas escolher uma. Faça o usuário especificar o diretório em vez de talvez escolher o diretório errado. (por exemplo, editar o script para definir o nome dir em vez de correr o código de detecção automática.)
Peter Cordes