Verifique se existe um arquivo com curinga no script de shell

Eu estou tentando verificar se existe um arquivo, mas com um curinga. Aqui está o meu exemplo:

if [ -f "xorg-x11-fonts*" ]; then printf "BLAH" fi 

Eu também tentei sem as aspas duplas.

O mais simples deve ser confiar no valor de retorno de ls (ele retorna um valor diferente de zero quando os arquivos não existem):

 if ls /path/to/your/files* 1> /dev/null 2>&1; then echo "files do exist" else echo "files do not exist" fi 

Eu redirecionei a saída ls para torná-la completamente silenciosa.


EDIT: Uma vez que esta resposta tem um pouco de atenção (e observações críticas muito úteis como comentários), aqui está uma otimização que também depende da expansão glob, mas evita o uso de ls :

 for f in /path/to/your/files*; do ## Check if the glob gets expanded to existing files. ## If not, f here will be exactly the pattern above ## and the exists test will evaluate to false. [ -e "$f" ] && echo "files do exist" || echo "files do not exist" ## This is all we needed to know, so we can break after the first iteration break done 

Isto é muito semelhante à resposta do @grk12, mas evita a iteração desnecessária em toda a lista.

Se o seu shell tiver uma opção nullglob e estiver ativada, um padrão curinga que não corresponda a nenhum arquivo será removido da linha de comando. Isso fará com que eu não veja nenhum argumento de caminho, liste o conteúdo do diretório atual e tenha sucesso, o que está errado. O stat GNU, que sempre falha se não houver argumentos ou um argumento que nomeie um arquivo inexistente, seria mais robusto. Além disso, o operador de redirecionamento &> é um bashismo.

 if stat --printf='' /path/to/your/files* 2>/dev/null then echo found else echo not found fi 

Melhor ainda é o GNU find , que pode manipular uma pesquisa curinga internamente e sair assim que encontrar um arquivo correspondente, em vez de perder tempo processando uma lista potencialmente grande deles expandida pelo shell; isso também evita o risco de que o shell possa estourar seu buffer de linha de comando.

 if test -n "$(find /dir/to/search -maxdepth 1 -name 'files*' -print -quit)" then echo found else echo not found fi 

Versões não-GNU do find podem não ter a opção -maxdepth usada aqui para fazer a pesquisa localizar apenas o diretório / dir / to / em vez de toda a tree de diretórios enraizada lá.

Aqui está minha resposta –

 files=(xorg-x11-fonts*) if [ -e "${files[0]}" ]; then printf "BLAH" fi 
 for i in xorg-x11-fonts*; do if [ -f "$i" ]; then printf "BLAH"; fi done 

Isso funcionará com vários arquivos e com espaços em branco nos nomes dos arquivos.

ATUALIZAR:

Ok, agora eu definitivamente tenho a solução:

 files=$(ls xorg-x11-fonts* 2> /dev/null | wc -l) if [ "$files" != "0" ] then echo "Exists" else echo "None found." fi > Exists 

Talvez isso ajude alguém:

 if [ "`echo xorg-x11-fonts*`" != "xorg-x11-fonts*" ]; then printf "BLAH" fi 

Você pode fazer o seguinte:

 set -- xorg-x11-fonts* if [ -f "$1" ]; then printf "BLAH" fi 

Isso funciona com sh e derivados: ksh e bash. Não cria nenhum sub-shell. Os comandos $ (..) e `…` criam um sub-shell: eles separam um processo e são ineficientes. Claro que funciona com vários arquivos, e essa solução pode ser a mais rápida ou a segunda mais rápida.

A questão não era específica para o Linux / Bash, então eu pensei em adicionar o modo Powershell – que trata caracteres curinga diferentes – você coloca nas aspas assim abaixo:

 If (Test-Path "./output/test-pdf-docx/Text-Book-Part-I*"){ Remove-Item -force -v -path ./output/test-pdf-docx/*.pdf Remove-Item -force -v -path ./output/test-pdf-docx/*.docx } 

Eu acho que isso é útil porque o conceito da pergunta original abrange “shells” em geral, não apenas Bash ou Linux, e também se aplica aos usuários do Powershell com a mesma pergunta.

Estritamente falando, se você quiser apenas imprimir “Blá”, aqui está a solução:

 find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 'BLAH' -quit 

Aqui está outro jeito:

 doesFirstFileExist(){ test -e "$1" } if doesFirstFileExist xorg-x11-fonts* then printf "BLAH" fi 

Mas eu acho que o mais ideal é o seguinte, porque ele não tentará classificar nomes de arquivos:

 if [ -z `find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 1 -quit` ] then printf "BLAH" fi 

O código bash que eu uso

 if ls /syslog/*.log > /dev/null 2>&1; then echo "Log files are present in /syslog/; fi 

Obrigado!

Aqui está uma solução para o seu problema específico que não requer loops ou comandos externos como ls , find e afins.

 if [ "$(echo xorg-x11-fonts*)" != "xorg-x11-fonts*" ]; then printf "BLAH" fi 

Como você pode ver, é apenas um pouco mais complicado do que você esperava, e confia no fato de que, se o shell não for capaz de expandir o glob, isso significa que não existem arquivos com esse glob e o echo produzirá o glob como é , o que nos permite fazer uma simples comparação de string para verificar se algum desses arquivos existe.

Se fôssemos generalizar o procedimento , deveríamos levar em conta o fato de que arquivos podem conter espaços dentro de seus nomes e / ou caminhos e que o char glob poderia simplesmente se expandir para nada (no seu exemplo, seria o caso de um arquivo cujo nome é exatamente xorg-x11-fonts).

Isto poderia ser alcançado pela seguinte function, no bash .

 function doesAnyFileExist { local arg="$*" local files=($arg) [ ${#files[@]} -gt 1 ] || [ ${#files[@]} -eq 1 ] && [ -e "${files[0]}" ] } 

Voltando ao seu exemplo, ele poderia ser chamado assim.

 if doesAnyFileExist "xorg-x11-fonts*"; then printf "BLAH" fi 

A expansão do Glob deve acontecer dentro da própria function para que ela funcione corretamente, é por isso que coloco o argumento entre aspas e é para isso que a primeira linha do corpo da function existe: para que vários argumentos (que poderiam ser o resultado de um glob expansão fora da function, bem como um parâmetro espúrio) seriam reunidos em um. Outra abordagem poderia ser gerar um erro se houver mais de um argumento, mas outro poderia ser ignorar todos, menos o primeiro argumento.

A segunda linha no corpo da function configura os files var para um array constituído por todos os nomes de arquivos que o glob expandiu, um para cada elemento do array. Tudo bem se os nomes dos arquivos contiverem espaços , cada elemento da matriz conterá os nomes como estão , incluindo os espaços.

A terceira linha no corpo da function faz duas coisas:

  1. Primeiro, verifica se há mais de um elemento na matriz. Se assim for, significa que a glob certamente se expandiu para algo (devido ao que fizemos na primeira linha), o que por sua vez implica que pelo menos um arquivo que corresponde à glob existe, o que é tudo o que queríamos saber.

  2. Se na etapa 1. descobrimos que obtivemos menos de 2 elementos na matriz, então verificamos se obtivemos uma e, em caso afirmativo, verificamos se essa existe, da maneira usual. Precisamos fazer essa verificação extra para considerar os argumentos da function sem glob chars, caso em que a matriz contém apenas um elemento unexpanded .

Eu uso isso:

 filescount=`ls xorg-x11-fonts* | awk 'END { print NR }'` if [ $filescount -gt 0 ]; then blah fi 

IMHO é melhor usar sempre find ao testar arquivos, globs ou diretórios. O obstáculo ao fazer isso é o status de saída de find : 0 se todos os caminhos foram percorridos com sucesso,> 0 caso contrário. A expressão que você passou a find não cria nenhum eco em seu código de saída.

O exemplo a seguir testa se um diretório possui inputs:

 $ mkdir A $ touch A/b $ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . && echo 'not empty' not empty 

Quando A não tem arquivos, o grep falha:

 $ rm A/b $ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . || echo 'empty' empty 

Quando A não existe, o grep falha novamente porque find somente prints para stderr:

 $ rmdir A $ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . && echo 'not empty' || echo 'empty' find: 'A': No such file or directory empty 

Substitua- -not -empty por qualquer outra expressão de find , mas tenha cuidado se você -exec um comando que imprime para stdout. Você pode querer grep para uma expressão mais específica em tais casos.

Essa abordagem funciona bem em scripts de shell. A pergunta inicial era procurar pelo glob xorg-x11-fonts* :

 if find -maxdepth 0 -name 'xorg-x11-fonts*' -print | head -n1 | grep -q . then : the glob matched else : ...not fi 

Note que o else-ramched é alcançado se xorg-x11-fonts* não foi find , ou encontrou um erro. Para distinguir o caso use $? .

 if [ `ls path1/* path2/* 2> /dev/null | wc -l` -ne 0 ]; then echo ok; else echo no; fi 

Tente isso

 fileTarget="xorg-x11-fonts*" filesFound=$(ls $fileTarget) # 2014-04-03 edit 2: removed dbl-qts around $(...) 

editar 2014-04-03 (removeu dbl-quotes e incluiu o arquivo de teste ‘Charlie 22.html’ (2 espaços)

 case ${filesFound} in "" ) printf "NO files found for target=${fileTarget}\n" ;; * ) printf "FileTarget Files found=${filesFound}\n" ;; esac 

Teste

 fileTarget="*.html" # where I have some html docs in the current dir FileTarget Files found=Baby21.html baby22.html charlie 22.html charlie21.html charlie22.html charlie23.html fileTarget="xorg-x11-fonts*" NO files found for target=xorg-x11-fonts* 

Observe que isso funciona somente no diretório atual ou onde o var fileTarget inclui o caminho que você deseja inspecionar.

E se

 if ls -l | grep -q 'xorg-x11-fonts.*' # grep needs a regex, not a shell glob then # do something else # do something else fi 

Se houver uma quantidade enorme de arquivos em uma pasta de rede usando o caractere curinga, é questionável (velocidade ou estouro de argumentos de linha de comando).

Acabei com:

 if [ -n "$(find somedir/that_may_not_exist_yet -maxdepth 1 -name \*.ext -print -quit)" ] ; then echo Such file exists fi 

Você também pode cortar outros arquivos

 if [ -e $( echo $1 | cut -d" " -f1 ) ] ; then ... fi 

Usando novos resources shmancy sofisticados em shells ksh, bash e zsh (este exemplo não manipula espaços em nomes de arquivos):

 # Declare a regular array (-A will declare an associative array. Kewl!) declare -a myarray=( /mydir/tmp*.txt ) array_length=${#myarray[@]} # Not found if the 1st element of the array is the unexpanded string # (ie, if it contains a "*") if [[ ${myarray[0]} =~ [*] ]] ; then echo "No files not found" elif [ $array_length -eq 1 ] ; then echo "File was found" else echo "Files were found" fi for myfile in ${myarray[@]} do echo "$myfile" done 

Sim, isso cheira a Perl. Ainda bem que não pisei nele;)

Encontrei um par de soluções interessantes que vale a pena compartilhar. O primeiro ainda sofre de “isso vai quebrar se houver muitos jogos” problema:

 pat="yourpattern*" matches=($pat) ; [[ "$matches" != "$pat" ]] && echo "found" 

(Lembre-se de que, se você usar uma matriz sem a syntax [ ] , obterá o primeiro elemento da matriz.)

Se você tiver “shopt -s nullglob” no seu script, você pode simplesmente fazer:

 matches=(yourpattern*) ; [[ "$matches" ]] && echo "found" 

Agora, se é possível ter uma tonelada de arquivos em um diretório, você está muito bem preso ao usar o find:

 find /path/to/dir -maxdepth 1 -type f -name 'yourpattern*' | grep -q '.' && echo 'found' 

teste de homem

 if [ -e file ]; then ... fi 

irá trabalhar para dir \ file.

Saudações