Como executar um comando sempre que um arquivo é alterado?


434

Eu quero uma maneira rápida e simples de executar um comando sempre que um arquivo for alterado. 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 ao terminal e pressionar Enter, sempre que salvar esse arquivo no meu editor. O que eu quero é algo como isto:

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 comando automático para executar algo no BufWrite, mas esse não é o tipo de solução que eu quero agora.

Atualização: eu quero algo simples, descartável, se possível. Além do mais, 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. Como preciso aceitar apenas um, aceito o que realmente usei (era simples, rápido e fácil de lembrar), mesmo sabendo que não é o mais elegante.


Possível duplicada entre sites: stackoverflow.com/questions/2972765/… (embora aqui esteja no tópico =))
Ciro Santilli

i já referenciado antes uma duplicata cross site e foi negado: S;)
Francisco Tapia

4
A solução de Jonathan Hartley baseia-se em outras soluções aqui e corrige grandes problemas que as respostas mais votadas têm: faltando algumas modificações e sendo ineficientes. Por favor, altere a resposta aceita a dele, que também está sendo mantido no github no github.com/tartley/rerun2 (ou a alguma outra solução sem essas falhas)
nealmcb

Respostas:


404

Simples, usando inotifywait (instale o inotify-toolspacote da sua distribuição ):

while inotifywait -e close_write myfile.py; do ./myfile.py; done

ou

inotifywait -q -m -e close_write myfile.py |
while read -r filename event; do
  ./myfile.py         # or "./$filename"
done

O primeiro snippet é mais simples, mas tem uma desvantagem significativa: ele perderá as alterações executadas enquanto inotifywaitnão estiver em execução (em particular enquanto myfileestiver em execução). O segundo trecho não possui esse defeito. No entanto, lembre-se de que o nome do arquivo não contenha espaço em branco. Se houver algum problema, use a --formatopção para alterar a saída para não incluir o nome do arquivo:

inotifywait -q -m -e close_write --format %e myfile.py |
while read events; do
  ./myfile.py
done

De qualquer maneira, há uma limitação: se algum programa substitui myfile.pycom um arquivo diferente, ao invés de escrever ao existente myfile, inotifywaitvai morrer. Muitos editores trabalham dessa maneira.

Para superar essa limitação, use inotifywaitno diretório:

inotifywait -e close_write,moved_to,create -m . |
while read -r directory events filename; do
  if [ "$filename" = "myfile.py" ]; then
    ./myfile.py
  fi
done

Como alternativa, use outra ferramenta que use a mesma funcionalidade subjacente, como incron (permite registrar eventos quando um arquivo é modificado) ou fswatch (uma ferramenta que também funciona em muitas outras variantes do Unix, usando o análogo de cada variante do inotify do Linux).


46
Encapsulei tudo isso (com alguns truques básicos ) em um sleep_until_modified.shscript simples de usar , disponível em: bitbucket.org/denilsonsa/small_scripts/src
Denilson Sá Maia

14
while sleep_until_modified.sh derivation.tex ; do latexmk -pdf derivation.tex ; doneé fantástico. Obrigado.
Rhys Ulerich

5
inotifywait -e delete_selfparece funcionar bem para mim.
Kos

3
É simples, mas tem duas questões importantes: Eventos podem ser perdidos (todos os eventos no loop) e a inicialização do inotifywait é feita sempre que torna a solução mais lenta para grandes pastas recursivas.
Wernight

6
Por alguma razão, while inotifywait -e close_write myfile.py; do ./myfile.py; donesempre sai sem executar o comando (bash e zsh). Para que isso funcionasse, eu precisava adicionar || true, por exemplo: while inotifywait -e close_write myfile.py || true; do ./myfile.py; done
ideasman42 3/16

166

entr ( http://entrproject.org/ ) fornece uma interface mais amigável para inotify (e também suporta * BSD e Mac OS X).

Torna muito fácil especificar vários arquivos para assistir (limitado apenas por ulimit -n), elimina o incômodo de lidar com a substituição de arquivos e requer menos sintaxe do bash:

$ find . -name '*.py' | entr ./myfile.py

Eu o uso em toda a árvore de origem do projeto para executar os testes de unidade do código que estou modificando no momento, e isso já foi um grande impulso para o meu fluxo de trabalho.

Sinalizadores como -c(limpar a tela entre execuções) e -d(sair quando um novo arquivo é adicionado a um diretório monitorado) adicionam ainda mais flexibilidade, por exemplo, você pode:

$ while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; done

A partir do início de 2018, ele ainda está em desenvolvimento ativo e pode ser encontrado no Debian & Ubuntu ( apt install entr); construção do repositório do autor foi indolor em qualquer caso.


3
Não lida com novos arquivos e suas modificações.
Wernight 29/04

2
@Wernight - a partir de 7 de maio de 2014, entra a nova -dbandeira; é um pouco mais demorado, mas você pode fazer while sleep 1 ; do find . -name '*.py' | entr -d ./myfile.py ; donepara lidar com novos arquivos.
Paul Fenney

1
entr também está disponível em repos debian pelo menos do debian Jessie / 8.2 na ...
Peter V. Mørch

5
melhor que encontrei no OS X, com certeza. fswatch agarra muitos eventos funk e eu não quero gastar o tempo para descobrir o porquê
dtc

5
Vale a pena notar que entr é acessível em Homebrew, então brew install entrfuncionará como esperado
jmarceli

108

Eu escrevi um programa Python para fazer exatamente isso chamado quando alterado .

O uso é simples:

when-changed FILE COMMAND...

Ou para assistir a vários arquivos:

when-changed FILE [FILE ...] -c COMMAND

FILEpode ser um diretório Assista recursivamente com -r. Use %fpara passar o nome do arquivo para o comando


1
@ysangkok sim ele faz, na versão mais recente do código :)
joh

4
Agora disponível em "instalar pip quando alterado". Ainda funciona bem. Obrigado.
AL Flanagan

2
Para limpar a tela primeiro, você pode usar when-changed FILE 'clear; COMMAND'.
Dave James Miller

1
Essa resposta é muito melhor, porque eu também posso fazê-lo no Windows. E esse cara realmente escreveu um programa para obter a resposta.
Wolfpack'08

4
Boas notícias, pessoal! when-changedagora é multiplataforma! Confira a versão mais recente do 0.3.0 :)
joh

52

E esse script? Ele usa o statcomando para obter o tempo de acesso de um arquivo e executa um comando sempre que houver uma alteração no tempo de acesso (sempre que o arquivo for acessado).

#!/bin/bash

### Set initial time of file
LTIME=`stat -c %Z /path/to/the/file.txt`

while true    
do
   ATIME=`stat -c %Z /path/to/the/file.txt`

   if [[ "$ATIME" != "$LTIME" ]]
   then    
       echo "RUN COMMAND"
       LTIME=$ATIME
   fi
   sleep 5
done

2
A stathora modificada não seria uma resposta melhor "sempre que um arquivo for alterado"?
Xen2050 27/02

1
A execução do stat muitas vezes por segundo causaria muitas leituras no disco? ou a chamada do sistema fstat tornaria automaticamente essas respostas para o cache de alguma forma? Eu estou tentando escrever uma espécie de 'relógio grunhido' para compilar meu código c sempre que fazer mudanças
Oskenso Kashi

Isso é bom se você souber o nome do arquivo a ser observado com antecedência. Melhor seria passar o nome do arquivo para o script. Melhor ainda seria se você pudesse passar muitos nomes de arquivos (por exemplo, "mywatch * .py"). Melhor ainda seria se ele pudesse operar recursivamente em arquivos em subdiretórios, o que algumas das outras soluções fazem.
9788 Jonathan -Jackley-

5
Caso alguém esteja se perguntando sobre leituras pesadas, testei esse script no Ubuntu 17.04 com um sono de 0,05s e vmstat -dobserve o acesso ao disco. Parece linux faz um trabalho fantástico em cache esse tipo de coisa: D
Oskenso Kashi

Não é erro de digitação no "comando", eu estava tentando corrigir, mas é o que diz "Editar não deve ser inferior a 6 caracteres"
user337085

30

Solução usando o Vim:

:au BufWritePost myfile.py :silent !./myfile.py

Mas eu não quero essa solução porque é meio chato digitar, é um pouco difícil lembrar o que digitar, exatamente, e é um pouco difícil desfazer seus efeitos (é necessário executar :au! BufWritePost myfile.py). Além disso, esta solução bloqueia o Vim até que o comando termine de executar.

Adicionei esta solução aqui apenas por questões de integridade, pois pode ajudar outras pessoas.

Para exibir a saída do programa (e interromper completamente o fluxo de edição, como a saída gravará sobre seu editor por alguns segundos, até você pressionar Enter), remova o :silentcomando.


1
Isso pode ser bastante agradável quando combinado com entr(veja abaixo) - apenas faça com que o vim toque em um arquivo fictício que entr está assistindo e deixe entr fazer o resto em segundo plano ... ou tmux send-keysse você estiver em um ambiente como esse :)
Paul Fenney

legais! você pode fazer uma macro para o seu .vimrcarquivo
ErichBSchulz

23

Se você npminstalou, nodemonprovavelmente é a maneira mais fácil de começar, especialmente no OS X, que aparentemente não possui ferramentas para inotificação. Ele suporta a execução de um comando quando uma pasta é alterada.


5
No entanto, ele apenas assiste arquivos .js e .coffee.
21412 zelk

6
A versão atual parece apoiar qualquer comando, por exemplo: nodemon -x "bundle exec rspec" spec/models/model_spec.rb -w app/models -w spec/models
kek

1
Eu gostaria de ter mais informações, mas OSX tem um método para rastrear mudanças, FSEvents
ConstantineK

1
No OS X, você também pode usar o Launch Daemons com uma WatchPathschave, como mostrado no meu link.
Adam Johns

19

Para aqueles que não podem instalar inotify-toolscomo eu, isso deve ser útil:

watch -d -t -g ls -lR

Este comando sairá quando a saída for alterada, ls -lRlistará todos os arquivos e diretórios com seu tamanho e datas; portanto, se um arquivo for alterado, ele deverá sair do comando, como o homem diz:

-g, --chgexit
          Exit when the output of command changes.

Sei que esta resposta pode não ser lida por ninguém, mas espero que alguém a atinja.

Exemplo de linha de comando:

~ $ cd /tmp
~ $ watch -d -t -g ls -lR && echo "1,2,3"

Abra outro terminal:

~ $ echo "testing" > /tmp/test

Agora o primeiro terminal será emitido 1,2,3

Exemplo de script simples:

#!/bin/bash
DIR_TO_WATCH=${1}
COMMAND=${2}

watch -d -t -g ls -lR ${DIR_TO_WATCH} && ${COMMAND}

5
Bom corte. Eu testei e parece ter um problema quando a listagem é longa e o arquivo alterado fica fora da tela. Uma pequena modificação pode ser algo assim: watch -d -t -g "ls -lR tmp | sha1sum"
Atle

3
se você assistir a sua solução a cada segundo, ele funciona sempre e executar MY_COMMAND somente se algumas mudanças de arquivo: assistir -N1 "assistir -d -t -g ls -lR && MY_COMMAND"
mnesarco

Minha versão do watch (no Linux watch from procps-ng 3.3.10) aceita segundos flutuantes por seu intervalo, portanto watch -n0.2 ..., pesquisará a cada quinto de segundo. Bom para os testes de unidade saudáveis ​​de milissegundos.
22418 Jonathan

15

rerun2( no github ) é um script Bash de 10 linhas no formato:

#!/usr/bin/env bash

function execute() {
    clear
    echo "$@"
    eval "$@"
}

execute "$@"

inotifywait --quiet --recursive --monitor --event modify --format "%w%f" . \
| while read change; do
    execute "$@"
done

Salve a versão do github como 'reexecutar' no seu PATH e invoque-a usando:

rerun COMMAND

Ele executa o COMMAND toda vez que há um evento de modificação do sistema de arquivos no diretório atual (recursivo).

Coisas que alguém pode gostar sobre isso:

  • Ele usa inotify, por isso é mais responsivo que a pesquisa. Fabuloso para executar testes de unidade abaixo de milissegundos ou renderizar arquivos de pontos gráficos, sempre que você clicar em 'salvar'.
  • Por ser tão rápido, você não precisa se preocupar em dizer para ignorar subdiretórios grandes (como node_modules) apenas por razões de desempenho.
  • É extremamente super responsivo, porque só chama inotifywait uma vez, na inicialização, em vez de executá-lo e incorrendo no caro custo de estabelecer relógios, em todas as iterações.
  • São apenas 12 linhas de Bash
  • Por ser o Bash, ele interpreta os comandos que você passa exatamente como se os tivesse digitado em um prompt do Bash. (Presumivelmente, isso é menos legal se você usar outro shell.)
  • Ele não perde eventos que acontecem enquanto o COMMAND está em execução, ao contrário da maioria das outras soluções inotify nesta página.
  • No primeiro evento, ele entra em um 'período morto' por 0,15 segundo, durante o qual outros eventos são ignorados, antes que o COMMAND seja executado exatamente uma vez. Isso ocorre para que a enxurrada de eventos causados ​​pela dança de criar-gravar-mover que Vi ou Emacs faz ao salvar um buffer não cause várias execuções trabalhosas de um conjunto de testes possivelmente lento. Quaisquer eventos que ocorrerem enquanto o COMMAND estiver em execução não serão ignorados - eles causarão um segundo período morto e a execução subsequente.

Coisas que alguém pode não gostar:

  • Ele usa inotify, portanto não funciona fora do Linuxland.
  • Por usar o inotify, ele tentará assistir a diretórios que contêm mais arquivos do que o número máximo de relógios inotify do usuário. Por padrão, isso parece estar definido entre 5.000 e 8.000 em máquinas diferentes que eu uso, mas é fácil aumentar. Consulte https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-reached
  • Falha ao executar comandos contendo aliases do Bash. Eu poderia jurar que isso costumava funcionar. Em princípio, como esse é o Bash, não executando COMMAND em um subshell, eu esperaria que isso funcionasse. Eu adoraria ouvir Se alguém sabe por que não. Muitas das outras soluções nesta página também não podem executar esses comandos.
  • Pessoalmente, eu gostaria de poder pressionar uma tecla no terminal em que está sendo executado para causar manualmente uma execução extra do COMMAND. Eu poderia adicionar isso de alguma forma, simplesmente? Um loop 'while read -n1' em execução simultaneamente que também chama de execute?
  • No momento, eu o codifiquei para limpar o terminal e imprimir o COMMAND executado em cada iteração. Algumas pessoas podem querer adicionar sinalizadores de linha de comando para desativar coisas assim, etc. Mas isso aumentaria muitas vezes o tamanho e a complexidade.

Este é um refinamento da resposta de @ cychoi.


2
Eu acredito que você deve usar em "$@"vez de $@, a fim de trabalhar corretamente com argumentos que contêm espaços. Mas, ao mesmo tempo que você usa eval, o que força o usuário a executar novamente a ter um cuidado extra ao citar.
Denilson Sá Maia

Obrigado Denilson. Você poderia dar um exemplo de onde a citação precisa ser feita com cuidado? Eu tenho usado ele nas últimas 24 horas e não vi nenhum problema com espaços até agora, nem citei nada com cuidado - apenas invocado como rerun 'command'. Você está apenas dizendo que, se eu usasse "$ @", o usuário poderia chamar como rerun command(sem aspas?) Isso não parece tão útil para mim: geralmente não quero que o Bash faça nenhum processamento de comando antes de passá-lo para executar novamente. por exemplo, se o comando contiver "echo $ myvar", desejarei ver os novos valores de myvar em cada iteração.
Jonathan Hartley

1
Algo como rerun foo "Some File"pode quebrar. Mas como você está usando eval, ele pode ser reescrito como rerun 'foo "Some File". Observe que, às vezes, a expansão do caminho pode introduzir espaços: rerun touch *.fooprovavelmente será interrompida, e o uso rerun 'touch *.foo'tem semântica ligeiramente diferente (a expansão do caminho ocorre apenas uma vez ou várias vezes).
Denilson Sá Maia

Obrigado pela ajuda. Sim: rerun ls "some file"quebra por causa dos espaços. rerun touch *.foo*normalmente funciona bem, mas falha se os nomes de arquivos que correspondem a * .foo contiverem espaços. Obrigado por me ajudar a ver como rerun 'touch *.foo'tem semântica diferente, mas suspeito que a versão com aspas simples seja a semântica que eu quero: quero que cada iteração de reexecução aja como se eu tivesse digitado o comando novamente - portanto, quero *.foo ser expandida em cada iteração . Vou tentar as suas sugestões para examinar os seus efeitos ...
Jonathan Hartley

Mais discussões sobre este PR ( github.com/tartley/rerun2/pull/1 ) e outros.
Jonathan Hartley

12

Aqui está um simples shell shell Bourne que:

  1. Leva dois argumentos: o arquivo a ser monitorado e um comando (com argumentos, se necessário)
  2. Copia o arquivo que você está monitorando no diretório / tmp
  3. Verifica a cada dois segundos se o arquivo que você está monitorando é mais recente que a cópia
  4. Se for mais recente, substitui a cópia pelo original mais recente e executa o comando
  5. Limpa depois de pressionar quando você pressiona Ctr-C

    #!/bin/sh  
    f=$1  
    shift  
    cmd=$*  
    tmpf="`mktemp /tmp/onchange.XXXXX`"  
    cp "$f" "$tmpf"  
    trap "rm $tmpf; exit 1" 2  
    while : ; do  
        if [ "$f" -nt "$tmpf" ]; then  
            cp "$f" "$tmpf"  
            $cmd  
        fi  
        sleep 2  
    done  
    

Isso funciona no FreeBSD. O único problema de portabilidade que consigo pensar é se algum outro Unix não possui o comando mktemp (1), mas nesse caso você pode apenas codificar o nome do arquivo temporário.


9
A pesquisa é a única maneira portátil, mas a maioria dos sistemas possui um mecanismo de notificação de alteração de arquivos (inotify no Linux, kqueue no FreeBSD, ...). Você tem um problema grave de cotação $cmd, mas felizmente isso é facilmente corrigível: abandone a cmdvariável e execute "$@". Seu script não é adequado para monitorar um arquivo grande, mas pode ser corrigido substituindo cppor touch -r(você só precisa da data, não do conteúdo). Em termos de portabilidade, o -ntteste requer bash, ksh ou zsh.
Gilles

8

Dê uma olhada no incron . É semelhante ao cron, mas usa eventos inotify em vez de tempo.


Isso pode ser feito para funcionar, mas criar uma entrada do incron é um processo bastante trabalhoso em comparação com outras soluções nesta página.
Jonathan Hartley

6

Outra solução com os NodeJs, fsmonitor :

  1. Instalar

    sudo npm install -g fsmonitor
    
  2. Na linha de comando (exemplo, monitore logs e "revenda" se um arquivo de log for alterado)

    fsmonitor -s -p '+*.log' sh -c "clear; tail -q *.log"
    

Nota lateral: o exemplo pode ser resolvido tail -F -q *.log, eu acho.
Volker Siegel

Foi apenas para dar um exemplo, tail -fnão é clearo terminal.
Atika

6

Veja o Guard, em particular com este plugin:

https://github.com/hawx/guard-shell

Você pode configurá-lo para assistir a qualquer número de padrões no diretório do seu projeto e executar comandos quando ocorrerem alterações. Boa chance, mesmo que haja um plugin disponível para o que você está tentando fazer em primeiro lugar.


6

se você tiver o nodemon instalado, poderá fazer o seguinte:

nodemon -w <watch directory> -x "<shell command>" -e ".html"

No meu caso, edito o html localmente e o envio para o servidor remoto quando um arquivo é alterado.

nodemon -w <watch directory> -x "scp filename jaym@jay-remote.com:/var/www" -e ".html"

6

No Linux:

man watch

watch -n 2 your_command_to_run

Irá executar o comando a cada 2 segundos.

Se o seu comando levar mais de 2 segundos para ser executado, o relógio aguardará até que seja concluído antes de executar novamente.


Isso é bastante simples, embora seja um desperdício, é fácil para tarefas de desenvolvimento como fazer alterações ao vivo de estilos.
Xeoncross

2
O que acontece quando o comando leva mais de dois segundos para ser executado?
Trintathreeforty

@thirtythreeforty Uma rápida experiência no Ubuntu mostra que o relógio aguarda os dois segundos completos, não importa quanto tempo o comando leve para executar. FWIW, o período de suspensão pode ser especificado com '-n', até um mínimo de 0,1 segundos.
Jonathan Hartley

5

Watchdog é um projeto Python e pode ser exatamente o que você está procurando:

Plataformas suportadas

  • Linux 2.6 (inotify)
  • Mac OS X (eventos FSE, kqueue)
  • FreeBSD / BSD (kqueue)
  • Windows (ReadDirectoryChangesW com portas de conclusão de E / S; threads de trabalho ReadDirectoryChangesW)
  • Independente do sistema operacional (pesquisando no disco os instantâneos de diretório e comparando-os periodicamente; lento e não recomendado)

Acabei de escrever um wrapper de linha de comando para ele watchdog_exec:

Execuções de exemplo

No evento fs que envolve arquivos e pastas no diretório atual, execute o echo $src $dstcomando, a menos que o evento fs seja modificado, execute o python $srccomando.

python -m watchdog_exec . --execute echo --modified python

Usando argumentos curtos e restringindo a execução apenas quando eventos envolvem " main .py":

python -m watchdog_exec . -e echo -a echo -s __main__.py

EDIT: Acabei de encontrar Watchdog tem uma CLI oficial chamada watchmedo, então confira também.


4

Se o seu programa gerar algum tipo de log / saída, você pode criar um Makefile com uma regra para esse log / saída que depende do seu script e fazer algo como

while true; do make -s my_target; sleep 1; done

Como alternativa, você pode criar um alvo falso e ter a regra de que ele chame seu script e toque no alvo falso (enquanto ainda depende do seu script).


11
while sleep 1 ; do something ; doneé um pouco melhor que while true ; do something ; sleep 1 ; done. Pelo menos para com facilidade ao pressionar Ctrl + C.
Denilson Sá Maia

Remover o sono causa um loop ocupado (CPU gerando calor e prejudicando a vida da bateria em um laptop)?
Steven Lu

2
@StevenLu: não, o sono não é uma espera ocupada. O problema é que, se o sono estiver no corpo, o Control-C matará o sono e o loop começará novamente. O uso de energia para iniciar o loop over é insignificante. Tente você mesmo em um terminal. Você precisa segurar o Control-C para que ele funcione, se você dorme no corpo.
Janus Troelsen

Direito. Acho que perdi e não vi que o sono ainda está presente como condição de loop. Esse pequeno ajuste é incrível.
Steven Lu

4

2
Este é um script Bash de 200 linhas, cheio de recursos, que pesquisa statos nomes de arquivos fornecidos, executa md5sumna saída e executa novamente o comando fornecido, se esse valor for alterado. Por ser o Bash, suspeito que ele execute um bom comando exatamente como se você o digitasse em um prompt do Bash. (Em contraste, a maioria das soluções aqui escritos em outras línguas falhará para executar comandos que, por exemplo, contêm pseudónimos de casca, tais como ll)
Jonathan Hartley

4

Melhorado com a resposta de Gilles .

Esta versão é executada inotifywaituma vez e monitora os eventos (.eg:) modifyposteriormente. Tais que inotifywait não precisam ser reexecutados a cada evento encontrado.

É rápido e rápido! (mesmo ao monitorar diretório grande recursivamente)

inotifywait --quiet --monitor --event modify FILE | while read; do
    # trim the trailing space from inotifywait output
    REPLY=${REPLY% }
    filename=${REPLY%% *}
    # do whatever you want with the $filename
done

Esta é a melhor resposta na página para usuários somente do Linux. Substitua as coisas dentro do loop por 'execute $ @', e o usuário poderá chamar esse script passando seu próprio comando para executar. Até funciona com comandos que contêm aliases de shell, se você o utiliza, usando algo como ". Scriptname COMMAND". Isso ainda encontrará o scriptname no PATH.
Jonathan Hartley

Eu acho que você quer colocar 'enquanto lê RESPOSTA'?
Jonathan Hartley

1
obrigado pelo esclarecimento. Agradecemos a sua fase! Eu teria excluído esses comentários, mas é claro que agora não vou.
Jonathan Hartley

3

Um pouco mais sobre o lado da programação, mas você deseja algo como inotify . Existem implementações em vários idiomas, como jnotify e pyinotify .

Essa biblioteca permite monitorar arquivos únicos ou diretórios inteiros e retorna eventos quando uma ação é descoberta. As informações retornadas incluem o nome do arquivo, a ação (criar, modificar, renomear, excluir) e o caminho do arquivo, entre outras informações úteis.


3

Para aqueles que procuram uma solução FreeBSD, aqui está a porta:

/usr/ports/sysutils/wait_on

3

Eu gosto da simplicidade de, while inotifywait ...; do ...; doneno entanto, tem dois problemas:

  • As alterações de arquivo que ocorrerem durante o do ...;serão perdidas
  • Lento ao usar no modo recursivo

Para isso, criei um script auxiliar que usa inotifywait sem essas limitações: inotifyexec

Eu sugiro que você coloque esse script no seu caminho, como em ~/bin/. O uso é descrito apenas executando o comando.

Exemplo: inotifyexec "echo test" -r .


Atualizado o script para suportar a correspondência de padrões de regex.
Wernight

Ambos os problemas são resolvidos usando o inotifywait no modo "--monitor". Veja a resposta de cychoi.
Jonathan Hartley

3

Solução aprimorada de Sebastian com watchcomando:

watch_cmd.sh:

#!/bin/bash
WATCH_COMMAND=${1}
COMMAND=${2}

while true; do
  watch -d -g "${WATCH_COMMAND}"
  ${COMMAND}
  sleep 1     # to allow break script by Ctrl+c
done

Exemplo de chamada:

watch_cmd.sh "ls -lR /etc/nginx | grep .conf$" "sudo service nginx reload"

Funciona, mas tenha cuidado: o watchcomando conhece bugs (consulte man): ele reage às alterações apenas no VISÍVEL nas partes terminais da -g CMDsaída.


2

Você pode tentar reflexo .

O Reflex é uma pequena ferramenta para monitorar um diretório e executar novamente um comando quando certos arquivos são alterados. É ótimo para executar automaticamente tarefas de compilação / limpeza / teste e para recarregar seu aplicativo quando o código é alterado.

# Rerun make whenever a .c file changes
reflex -r '\.c$' make

Você pode citar / explicar um pouco sobre a ferramenta? Leia rapidamente como recomendar software para obter orientação.
bertieb

1

Uma resposta oneliner que estou usando para acompanhar uma alteração no arquivo:

$ while true ; do NX=`stat -c %Z file` ; [[ $BF != $NX ]] && date >> ~/tmp/fchg && BF=$NX || sleep 2 ; done

Você não precisa inicializar o BF se souber que a primeira data é a hora de início.

Isso é simples e portátil. Há outra resposta baseada na mesma estratégia usando um script aqui. Dê uma olhada também.


Uso: estou usando isso para depurar e ficar de olho ~/.kde/share/config/plasma-desktop-appletsrc; que por algum motivo desconhecido continua perdendo meuSwitchTabsOnHover=false


1

Eu uso esse script para fazer isso. Estou usando inotify no modo monitor

#!/bin/bash
MONDIR=$(dirname $1)
ARQ=$(basename $1)

inotifywait -mr -e close_write $MONDIR | while read base event file 
do
  if (echo $file |grep -i "$ARQ") ; then
    $1
  fi
done

Salve isso como runatwrite.sh

Usage: runatwrite.sh myfile.sh

ele executará myfile.sh a cada gravação.


1

Para aqueles que usam o OS X, você pode usar o LaunchAgent para observar um caminho / arquivo em busca de alterações e fazer algo quando isso acontecer. FYI - LaunchControl é um bom aplicativo para criar / modificar / remover daemons / agentes facilmente.

( exemplo retirado daqui )

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN
http://www.apple.com/DTDs/PropertyList-1.0.dtd>
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>test</string>
    <key>ProgramArguments</key>
    <array>
        <string>say</string>
        <string>yy</string>
    </array>
    <key>WatchPaths</key>
    <array>
        <string>~/Desktop/</string>
    </array>
</dict>
</plist>


0

Para as pessoas que acham isso pesquisando para alterações em um determinado arquivo, a resposta é muito mais simples (inspirado resposta de Gilles ).

Se você deseja fazer algo após a gravação de um arquivo específico, veja como:

while true; do
  inotifywait -e modify /path/to/file
  # Do something *after* a write occurs, e.g. copy the file
  /bin/cp /path/to/file /new/path
done

Salve isso como, por exemplo, copy_myfile.she coloque o .sharquivo na /etc/init.d/pasta para que ele seja executado na inicialização.


Compartilha o problema com a resposta de Giles, que é executada inotifywait em todas as iterações, o que pode não responder por assistir recursivamente a diretórios muito grandes. Veja a resposta de cychoi para a correção disso.
Jonathan Hartley


0

Como algumas outras pessoas fizeram, também escrevi uma ferramenta leve de linha de comando para fazer isso. Está totalmente documentado, testado e modular.

Watch-Do

Instalação

Você pode instalá-lo (se você tiver Python3 e 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')
  • Execute vários comandos em uma alteração de arquivo (basta especificar o -dsinalizador novamente)
  • Mantém dinamicamente a lista de arquivos para observar se o globbing é usado ( -rpara ativar isso)
  • Várias maneiras de "assistir" a 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ê deseja executar comandos, quando um arquivo é acessado, é trivial escrever seu próprio observador (mecanismo que determina se os executores devem ser executados).
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.