Como espero por um arquivo no script de shell?

14

Estou tentando escrever um shell script que esperará que um arquivo apareça no /tmpdiretório chamado sleep.txte, uma vez encontrado, o programa cessará, caso contrário, quero que o programa esteja no estado de suspensão (suspenso) até que o arquivo seja localizado . Agora, estou assumindo que usarei um comando de teste. Então, algo como

(if [ -f "/tmp/sleep.txt" ]; 
then stop 
   else sleep.)

Sou novato em escrever scripts de shell e qualquer ajuda é muito apreciada!

Mo Gainz
fonte
Olhe para $MAILPATH.
mikeserv

Respostas:

22

No Linux, você pode usar o subsistema inotify kernel para aguardar com eficiência a aparência de um arquivo em um diretório:

while read i; do if [ "$i" = sleep.txt ]; then break; fi; done \
   < <(inotifywait  -e create,open --format '%f' --quiet /tmp --monitor)
# script execution continues ...

(assumindo Bash para o <() sintaxe de redirecionamento de saída)

A vantagem dessa abordagem em comparação com a pesquisa por intervalo de tempo fixo, como em

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# script execution continues ...

é que o kernel dorme mais. Com uma especificação de evento inotify, create,openo script é agendado para execução quando um arquivo abaixo /tmpé criado ou aberto. Com a pesquisa de intervalo de tempo fixo, você desperdiça ciclos de CPU para cada incremento de tempo.

Incluí o openevento para registrar também touch /tmp/sleep.txtquando o arquivo já existe.

maxschlepzig
fonte
Obrigado, isso é incrível! Por que você precisa do loop while se sabe o nome do arquivo? Por que não poderia ter sido assim inotifywait -e create,open --format '%f' --quiet /tmp/sleep.txt --monitor > /dev/null ; # continues once /tmp/sleep.txt is created/opened?
NHDaly
Oh, jk. Depois de instalar a ferramenta, eu entendo agora. inotifywaité em si um loop while e gera uma linha toda vez que o arquivo é aberto / criado. Então você precisa que o loop while seja interrompido quando for alterado.
NHDaly
exceto que isso produz uma condição de corrida - diferente da versão test -e / sleep. Não há como garantir que o arquivo não seja criado logo antes da inserção do loop - nesse caso, o evento será perdido. Você precisaria adicionar algo como (sleep 1; [[-e /tmp/sleep.txt]] && toque em /tmp/sleep.txt;) & antes do loop. Que, naturalmente, pode criar problemas na estrada, dependendo lógica de negócio real
n-alexander
@ n-alexander Bem, a resposta pressupõe que você execute o snippet antes de iniciar o processo que produzirá sleep.txtem algum momento. Portanto, nenhuma condição de corrida. A pergunta não contém nada que indique um cenário que você tem em mente.
Maxschlepzig 02/07
@maxschlepzig ao contrário. A menos que o pedido seja imposto, ele não pode ser assumido. Fundamentos.
N / alexander
10

Basta colocar seu teste no whileloop:

while [ ! -f /tmp/sleep.txt ]; do sleep 1; done
# next command
jimmij
fonte
Então, como você escreveu, esta é a lógica: enquanto o arquivo não existe, o script dorme. Caso contrário, está feito. Corrigir?
Mo Gainz
@MoGainz Correct. O número depois sleepé o número de segundos para aguardar em cada iteração - você pode alterar isso dependendo de suas necessidades.
jimmij
7

Existem alguns problemas com algumas das inotifywaitabordagens baseadas em dados fornecidas até agora:

  • eles não conseguem encontrar um sleep.txtarquivo que foi criado primeiro como um nome temporário e depois renomeado para sleep.txt. É preciso combinar os moved_toeventos além decreate
  • os nomes dos arquivos podem conter caracteres de nova linha, imprimir os nomes dos arquivos delimitados por nova linha não é suficiente para determinar se um sleep.txtfoi criado. E se umfoo\nsleep.txt\nbar arquivo foi criado, por exemplo?
  • e se o arquivo for criado antes de inotifywait ter sido iniciado e instalado o relógio? Então inotifywaitesperaria eternamente por um arquivo que já está aqui. Você precisa garantir que o arquivo ainda não esteja lá após a instalação do relógio.
  • algumas das soluções continuam em inotifywaitexecução (pelo menos até que outro arquivo seja criado) após a localização do arquivo.

Para resolvê-los, você pode fazer:

sh -c 'echo "$$" &&
        LC_ALL=C exec inotifywait -me create,moved_to --format=/%f/ . 2>&1' | {
  IFS= read pid &&
    while IFS= read -r line && [ "$line" != "Watches established." ]; do
      : wait for watches to be established
    done
  [ -e sleep.txt ] || [ -L sleep.txt ] || grep -qxF /sleep.txt/ && kill "$pid"
}

Observe que estamos observando a criação de sleep.txtno diretório atual .(faça o mesmo cd /tmp || exitno seu exemplo). O diretório atual nunca muda; portanto, quando essa linha de tubulação retorna com êxito, ela está sleep.txtno diretório atual que foi criado.

Claro, você pode substituir .com /tmpacima, mas enquanto inotifywaitestiver em execução, /tmppoderia ter sido renomeado várias vezes (improvável para /tmp, mas algo a considerar no caso geral) ou um novo sistema de arquivos montado sobre ele, por isso, quando o gasoduto retorna, ele não pode seja um /tmp/sleep.txtque foi criado, mas um em /new-name-for-the-original-tmp/sleep.txtvez disso. Um novo /tmpdiretório também poderia ter sido criado no intervalo e esse não seria monitorado; portanto, um diretório sleep.txtcriado não seria detectado.

Stéphane Chazelas
fonte
1

A resposta aceita realmente funciona (graças ao maxschlepzig), mas deixa o monitoramento inotifywait em segundo plano até que o script saia. A única resposta que corresponde exatamente seus requisitos (ou seja, aguardar o sleep.txt aparecer em / tmp) parece ser de Stephane, se o diretório a ser monitorado pelo inotifywait for alterado de ponto (.) Para '/ tmp'.

No entanto, se você estiver disposto a usar SOMENTE um diretório temporário para colocar seu sinalizador sleep.txt e puder apostar que ninguém mais colocará nenhum arquivo nesse diretório, basta pedir à inotifywait que observe este diretório para criar criações de arquivos:

1º passo: crie o diretório que você irá monitorar:

directoryToPutSleepFile=$(mktemp -d)

2º passo: verifique se o diretório está realmente lá

until [ -d $directoryToPutSleepFile ]; do sleep 0.1; done

3º passo: aguarde até que QUALQUER arquivo apareça dentro $directoryToPutSleepFile

inotifywait -e create --format '%f' --quiet $directoryToPutSleepFile

O arquivo que você $directoryToPutSleepFileinserirá pode ter o nome sleep.txt awake.txt, qualquer que seja. O momento em que qualquer arquivo é criado dentro do $directoryToPutSleepFileseu script continuará além da inotifywaitdeclaração.

nikolaos
fonte
1
Mas isso poderia perder a criação do arquivo, se outro fosse criado pouco antes, causando a sleep.txtcriação entre duas invocações de espera inotifywait.
Stéphane Chazelas
veja minha resposta para uma maneira alternativa de solucioná-la.
Stéphane Chazelas
Por favor, tente o meu código. inotifywait é chamado apenas uma vez. Quando sleep.txt é criado (ou lido / gravado, se já existir), inotifywait gera "sleep.txt" e a execução continua após a instrução 'done'.
nikolaos
É chamado várias vezes até que um arquivo chamado sleep.txtseja criado. Tente, por exemplo touch /tmp/foo /tmp/sleep.txt, uma instância de inotifywaitreportará fooe sairá e, quando a próxima inotifywait for iniciada, o sleep.txt já estará lá por muito tempo, portanto a inotifywait não detectará sua criação.
Stéphane Chazelas
Você estava certo sobre as várias invocações: uma invocação para cada arquivo criado em / tmp. Eu atualizei minha resposta. Obrigado por seu comentário.
Nikolaos
0

em geral, é bastante difícil tornar confiável qualquer coisa, exceto o loop de teste / sono simples. O principal problema é que o teste será executado com a criação do arquivo - a menos que o teste seja repetido, o evento de criação poderá ser perdido. Essa é de longe a sua melhor aposta na maioria dos casos em que você usaria o Shell para começar:

#!/bin/bash
while [[ ! -e /tmp/file ]] ; do
    sleep 1
done

Se você achar que isso é insuficiente devido a problemas de desempenho, usar o Shell provavelmente não é uma boa idéia em primeiro lugar.

n-alexander
fonte
2
Esta é apenas uma duplicata da resposta de jimmij .
Maxschlepzig
0

Com base na resposta aceita de maxschlepzig (e em uma idéia da resposta aceita em /superuser/270529/monitoring-a-file-until-a-string-is-found ), proponho o seguinte aprimorado ( na minha opinião) resposta que também pode funcionar com tempo limite:

# Enable pipefail, so if the left side of the pipe fails it does not get silently ignored
set -o pipefail
( timeout 120 inotifywait -e create,open --format '%f' --quiet /tmp --monitor & ) | while read i; do if [ "$i" == 'sleep.txt' ]; then break; fi; done
EXIT_STATUS=$?
if [ "${EXIT_STATUS}" == '124' ]; then
  echo "Timeout happened"
fi

Caso o arquivo não seja criado / aberto dentro do tempo limite especificado, o status de saída será 124 (conforme a documentação do tempo limite (página de manual)). Caso seja criado / aberto, o status de saída é 0 (êxito).

Sim, o inotifywait é executado em um sub-shell dessa maneira e esse sub-shell só será executado quando o tempo limite ocorrer ou quando o script principal sair (o que ocorrer primeiro).

Attila123
fonte