Teste se há arquivos correspondentes a um padrão para executar um script


29

Estou tentando escrever uma ifdeclaração para testar se existem arquivos correspondentes a um determinado padrão. Se houver um arquivo de texto em um diretório, ele deverá executar um determinado script.

Meu código atualmente:

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

Por favor, dê algumas idéias; Eu só quero executar o script se houver um .txtno diretório


3
Você tem certeza de que "o diretório" deveria ser /? Além disso, falta um ponto e vírgula antes fi.
depquid

A solução mais limpa e robusta que encontrei é usar findcomo explicado aqui no stackoverflow .
Joshua Goldberg

Respostas:


39
[ -f /*.txt ]

retornará true somente se houver um (e apenas um) arquivo não oculto em /cujo nome termina .txte se esse arquivo for um arquivo regular ou um link simbólico para um arquivo regular.

Isso ocorre porque os curingas são expandidos pelo shell antes de serem passados ​​para o comando (aqui [).

Portanto, se há um /a.txte /b.txt, [serão passados 5 argumentos: [, -f, /a.txt, /b.txte ]. [reclamaria que -fé dado muitos argumentos.

Se você deseja verificar se o *.txtpadrão se expande para pelo menos um arquivo não oculto (regular ou não):

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 nullglobé bashespecífico, mas escudos como ksh93, zsh, yash, tcshtem declarações equivalentes.

Observe que ele encontra esses arquivos lendo o conteúdo do diretório; ele não tenta acessar esses arquivos, o que o torna mais eficiente do que soluções que chamam comandos como lsou statnessa lista de arquivos computados pelo shell.

O shequivalente padrão seria:

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

O problema é que, com os shells Bourne ou POSIX, se um padrão não corresponde, ele se expande. Portanto, se *.txtexpandir *.txt, você não sabe se é porque não há .txtarquivo no diretório ou porque existe um arquivo chamado *.txt. Usando[*].txt *.txt permite discriminar entre os dois.


[ -f /*.txt ]é bastante rápido em comparação com compgen.
Daniel Böhmer 23/11

@ DanielBöhmer [ -f /*.txt ]estaria errado, mas no meu teste em um diretório que contém 3425arquivos, 94dos quais arquivos txt não ocultos, compgen -G "*.txt" > /dev/null 2>&1parecem ser tão rápidos quanto set -- *.txt; [ "$#" -gt 0 ](20,5 segundos para ambos quando repetidos 10000 vezes no meu caso).
Stéphane Chazelas

11

Você sempre pode usar find :

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

Explicação:

  • find . : pesquisa no diretório atual
  • -maxdepth 1: não procurar subdiretórios
  • -type f : pesquise apenas arquivos regulares
  • name "*.txt" : procure arquivos que terminem em .txt
  • 2>/dev/null : redirecionar mensagens de erro para /dev/null
  • | grep -q . : grep para qualquer caractere, retornará false se nenhum caractere for encontrado.
  • && ./script: Executar ./scriptapenas se o comando anterior tiver sido bem-sucedido ( &&)

2
findsó retorna false se tiver problemas para procurar arquivos, não se não encontrar nenhum arquivo. Você deseja canalizar a saída grep -q .para verificar se encontra alguma coisa.
Stéphane Chazelas

@StephaneChazelas você está certo, é claro. Estranho, eu tinha testado e parecia funcionar. Deve ter feito algo estranho, porque isso não acontece mais. Quando encontrará "problemas para encontrar arquivos"?
terdon

@terdon, como quando algum diretório está inacessível, erros de E / S ou qualquer erro retornado por qualquer chamada do sistema que ele fizer. Nesse caso, tente depois chmod a-x ..
Stéphane Chazelas

8

Uma solução possível também é o Bash compgen. Esse comando retorna todas as correspondências possíveis para um padrão globbing e possui um código de saída indicando se algum arquivo correspondeu.

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

Encontrei essa pergunta enquanto procurava soluções mais rápidas.


1
Boa descoberta! Se você estiver em um código de idioma de vários bytes, poderá melhorar um pouco com LC_ALL=C compgen -G "*.txt" > /dev/null.
Stéphane Chazelas

7

Aqui está uma lista para fazê-lo:

$ ls
file1.pl  file2.pl

existem arquivos

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

arquivos não existem

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

Essa abordagem utiliza os operadores ||e &&no bash. Estes são os operadores "ou" e "e".

Portanto, se o comando stat retornar $?igual a 0, o primeiro echoserá chamado, se retornar 1, o segundoecho será chamado.

retornar 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 questão é amplamente abordada no stackoverflow:


1
Por que usar o não padrão statquando ls -dpode fazer o mesmo?
Stéphane Chazelas

Eu pensei que ls -dlista um diretório? Não parecia funcionar quando tentei listar um diretório com arquivos, ls -d *.plpor exemplo.
Slm

Você pode substituir a declaração à esquerda &&por ls *.txte vai funcionar tão bem. Certifique-se de enviar o stdout e o stderr para /dev/nullo sugerido por @slm.
unxnut

1
Se você usar ls *.txte não há arquivos presentes no diretório isso irá retornar uma $? = 2, que continuará a funcionar com o caso, em seguida, mas esta foi uma das minhas razões para escolher statmais ls. Eu queria um 0 para o sucesso e um 1 para um fracasso.
Slm

ls -dé listar diretórios em vez de seu conteúdo. O ls -dmesmo acontece lstatcom o arquivo, assim como o GNU stat. O que os comandos de status de saída diferente de zero retornam em caso de falha é específico do sistema; faz pouco sentido fazer suposições sobre eles.
Stéphane Chazelas

4

Como Chazelas aponta, seu script falharia se a expansão de curinga corresponder a mais de um arquivo.

No entanto, há um truque que eu uso ( mesmo que eu não goste muito ) para contornar:

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

Como funciona?

A expansão de curinga corresponderá a uma matriz de nomes de arquivos; obteremos o primeiro, se houver algum; caso contrário, nulo, se não houver correspondência.


IMO, esta é a resposta menos ruim aqui. Todos eles parecem bastante horríveis, como se um recurso básico estivesse faltando no idioma.
plugwash

@plugwash é intencional ... * Os scripts nix shell têm algum controle básico de fluxo e algumas outras chances e fins, mas no final do dia, seu trabalho é colar outros comandos. Se o bash é uma porcaria ... é porque os comandos que você usa com ele chupar
cb88

2
Essa é a lógica errada (e você está faltando aspas). Isso verifica se o primeiro arquivo correspondente é um arquivo regular. Pode ser um arquivo não regular, mas pode haver vários outros .txtarquivos do tipo regular . Tente, por exemplo, depois mkdir a.txt; mkfifo b.txt; echo regular > c.txt.
Stéphane Chazelas


0

Gosto da solução anterior do array, mas isso pode se tornar um desperdício com um grande número de arquivos - o shell usaria uma grande quantidade de memória para criar o array, e apenas o primeiro elemento seria testado.

Aqui está uma estrutura alternativa que eu testei ontem:

$ 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

O valor de saída do grep parece estar determinando o caminho através da estrutura de controle. Isso também testa com expressões regulares em vez de padrões de shell. Alguns dos meus sistemas possuem o comando "pcregrep", que permite correspondências de regex muito mais sofisticadas.

(Eu editei esta resposta para remover um "ls" na substituição de comando depois de ler as críticas acima para analisá-las.)


-2

se você quiser usar uma cláusula if, avalie a contagem:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.