Como executar um comando sempre que um arquivo é alterado?

408

Eu quero uma maneira rápida e simples de executar um comando sempre que um arquivo for alterado. Eu quero algo muito simples, algo que deixarei em execução em um terminal e fechá-lo sempre que terminar de trabalhar com esse arquivo.

Atualmente, estou usando isso:

while read; do ./myfile.py ; done

E então eu preciso ir para aquele terminal e pressionar Enter , sempre que eu salvar esse arquivo no meu editor. O que eu quero é algo assim:

while sleep_until_file_has_changed myfile.py ; do ./myfile.py ; done

Ou qualquer outra solução tão fácil quanto isso.

BTW: Estou usando o Vim, e sei que posso adicionar um autocommand para executar algo no BufWrite, mas esse não é o tipo de solução que quero agora.

Atualização: quero algo simples, descartável, se possível. Além disso, quero que algo seja executado em um terminal porque quero ver a saída do programa (quero ver mensagens de erro).

Sobre as respostas: Obrigado por todas as suas respostas! Todos eles são muito bons, e cada um tem uma abordagem muito diferente dos outros. Desde que eu preciso aceitar apenas um, estou aceitando o que eu realmente usei (foi simples, rápido e fácil de lembrar), mesmo sabendo que não é o mais elegante.

    
por Denilson Sá Maia 27.08.2010 / 22:02

35 respostas

0

A ferramenta 'fido' pode ser mais uma opção para essa necessidade. Consulte o link

    
por 20.03.2017 / 14:52
0

Como alguns outros já fizeram, também escrevi uma ferramenta de linha de comando leve para fazer isso. É totalmente documentado, testado e modular.

Watch-Do

Instalação

Você pode instalá-lo (se você tiver o Python3 e o pip) usando:

pip3 install git+https://github.com/vimist/watch-do

Uso

Use-o imediatamente executando:

watch-do -w my_file -d 'echo %f changed'

Visão geral dos recursos

  • Suporta globbing de arquivos (use -w '*.py' ou -w '**/*.py' )
  • Executar vários comandos em uma alteração de arquivo (basta especificar o sinalizador -d novamente)
  • Mantém dinamicamente a lista de arquivos a serem observados se a globbing for usada ( -r para ativar isso)
  • Várias maneiras de "assistir" um arquivo:
    • Hora da modificação (padrão)
    • Hash de arquivo
    • Trivial para implementar o seu próprio (este é o observador ModificationTime )
  • Design modular. Se você quer ter comandos em execução, quando um arquivo é acessado, é trivial escrever seu próprio observador (mecanismo que determina se os executores devem ser executados).
por 03.06.2017 / 14:10
0

Uso básico

Aqui está uma solução que não requer a instalação de mais software e funciona imediatamente.

tail -q --follow=name myfile.txt | head -n 0

Este comando sai nas seguintes condições:

  • Uma linha é adicionada ao myfile.txt depois que o comando é executado
  • O myfile.txt é substituído por outro após o comando ser executado

Você diz que está usando o vim e o vim substituirá o arquivo ao salvar. Eu testei isso funciona com o vim.

Você pode ignorar a saída deste comando, pode mencionar algo como:

tail: ‘myfile.txt’ has been replaced; following end of new file

Uso avançado

Você pode combinar isso com timeout para retornar verdadeiro ou falso. Você pode usá-lo assim:

timeout 5s bash -c 'tail -q --follow=name pipe 2> /dev/null | head -n 0' && echo changed || echo timeout

Discussão

tail usa inotify sob o capô. É assim que você obtém esse comportamento assíncrono sofisticado sem nenhuma pesquisa. Provavelmente existe algum outro programa unix padrão que usa inotify , que podemos abusar mais elegantemente.

Às vezes, esses comandos saem imediatamente, mas se você executá-los imediatamente uma segunda vez, eles funcionam como anunciados. Eu cometi um erro off-one em algum lugar, por favor me ajude a corrigir isso.

No RHEL, posso usar:

timeout 5s sh -c 'gio monitor pipe | head -n 0' && echo changed || echo timeout

Mas não tenho certeza se isso é portátil.

    
por 19.03.2018 / 21:30
0

Eu escrevi um programa em Python para fazer exatamente isso, chamado rerun .

UPDATE: Esta resposta é um script Python que pesquisa alterações, o que é útil em algumas circunstâncias. Para um script Bash somente do Linux que usa inotify, veja minha outra resposta, procure por esta página 'rerun2'.

Instale o Python2 ou o Python3 com:

pip install --user rerun

e o uso é muito simples:

rerun "COMMAND"

O comando é esperado como um único argumento, não uma sequência de argumentos separados por espaços. Daí citá-lo como mostrado, o que reduz qualquer fuga extra que você teria que adicionar. Basta digitar o comando como você o digitaria na linha de comando, mas entre aspas.

Por padrão, ele observa todos os arquivos dentro ou fora do diretório atual, pulando coisas como diretórios de controle de origem conhecidos, .git, .svn, etc.

Os sinalizadores opcionais incluem '-i NAME', que ignora as alterações nos arquivos ou diretórios nomeados. Isso pode ser dado várias vezes.

Como é um script Python, ele precisa executar o comando como um subprocesso, e usamos uma nova instância do shell atual do usuário para interpretar 'COMMAND' e decidir qual processo realmente será executado. No entanto, se o seu comando contiver aliases de shell e similares que são definidos em .bashrc, eles não serão carregados pela subshell. Para corrigir isso, você pode executar novamente um sinalizador '-I', para usar subshells interativos (também conhecidos como 'login'). Isso é mais lento e propenso a erros do que iniciar um shell normal, porque ele precisa originar seu .bashrc.

Eu uso com o Python 3, mas a última vez que verifiquei a reexecução ainda funcionou com o Python 2.

Espada de dois gumes é que usa polling ao invés de inotify. No lado positivo, isso significa que funciona em todos os sistemas operacionais. Além disso, é melhor do que algumas outras soluções mostradas aqui em termos de executar apenas o comando fornecido uma vez para um monte de alterações no sistema de arquivos, não uma vez por arquivo modificado, e ao mesmo tempo executar o comando uma segunda vez se algum arquivo mudar novamente enquanto o comando está sendo executado.

No lado negativo, poll significa que há uma latência de 0.0 a 1.0 segundo e, é claro, é lento monitorar diretórios extremamente grandes. Tendo dito isso, eu nunca encontrei um projeto grande o suficiente para que isso seja notável, desde que você use '-i' para ignorar coisas grandes como o virtualenv e o node_modules.

Hmmm. rerun tem sido indispensável para mim por anos - eu basicamente uso oito horas todos os dias para rodar testes, reconstruir arquivos de ponto conforme eu os edito, etc. Mas agora eu vou digitar isso aqui, é claro que eu preciso mudar a uma solução que usa inotify (não uso mais o Windows ou o OSX.) e está escrita em Bash (para que funcione com aliases sem qualquer alteração extra).

    
por 09.09.2015 / 21:36
0

Descrição

Isto irá assistir a um arquivo para mudanças e executar qualquer comando (incluindo argumentos adicionais) foi dado como segunda instrução. Também limpará a tela e imprimirá a hora da última execução. Nota: você pode tornar a função mais (ou menos) reativa, alterando o número de segundos que a função deve dormir após cada ciclo de loop while.

Exemplo de uso

watch_file my_file.php php my_file.php

Esta linha irá assistir a um arquivo php my_file.php e passar pelo interpretador php sempre que for alterado.


Definição de funções

function watch_file (){

### Set initial time of file
LTIME='stat -c %Z $1'
printf "3c"
echo -e "watching: $1 ---- $(date '+%Y-%m-%d %H:%M:%S')\n-------------------------------------------\n"
${@:2}

while true
do
   ATIME='stat -c %Z $1'

   if [[ "$ATIME" != "$LTIME" ]]
   then
    printf "3c"
    echo -e "watching: $1 ---- $(date '+%Y-%m-%d %H:%M:%S')\n-------------------------------------------\n"
    ${@:2}
    LTIME=$ATIME
   fi
   sleep 1
done
}

Crédito

Esta é basicamente uma versão mais geral da resposta do VDR.

    
por 12.04.2018 / 20:32