2010-08-27 20:02:40 +0000 2010-08-27 20:02:40 +0000
459
459

Como executar um comando sempre que um ficheiro é alterado?

Quero uma forma rápida e simples de executar um comando sempre que um ficheiro é alterado. Quero algo muito simples, algo que vou deixar a correr num terminal e fechá-lo sempre que acabar de trabalhar com esse ficheiro.

Actualmente, estou a usar isto:

while read; do ./myfile.py ; done

E depois preciso de ir a esse terminal e carregar em Enter, sempre que guardo esse ficheiro 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 como isto.

BTW: Estou a usar o Vim, e sei que posso adicionar um autocomando para executar algo no BufWrite, mas este não é o tipo de solução que eu quero agora.

Update: Eu quero algo simples, descartável se possível. Além disso, quero algo para correr num terminal porque quero ver a saída do programa (quero ver mensagens de erro).

Sobre as respostas: Obrigado por todas as vossas respostas! Todas elas são muito boas, e cada uma tem uma abordagem muito diferente das outras. Como só preciso de aceitar uma, estou a aceitar a que realmente usei (foi simples, rápida e fácil de lembrar), apesar de saber que não é a mais elegante.

Respostas (37)

434
434
434
2010-08-27 20:54:55 +0000

Simples, utilizando inotifywait (instale o pacote inotify-tools da sua distribuição):

while inotifywait -e close_write myfile.py; do ./myfile.py; done
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: perderá as alterações efectuadas enquanto inotifywait não estiver a funcionar (em particular enquanto myfile estiver a funcionar). O segundo snippet não tem este defeito. No entanto, tenha em atenção que assume que o nome do ficheiro não contém espaço em branco. Se isso for um problema, use a opção --format para alterar a saída para não incluir o nome do ficheiro:

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

Seja como for, existe uma limitação: se algum programa substituir o myfile.py por um ficheiro diferente, em vez de escrever para o myfile existente, o inotifywait morrerá. Muitos editores trabalham dessa forma.

Para ultrapassar esta limitação, use inotifywait no directó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

Alternativamente, use outra ferramenta que use a mesma funcionalidade subjacente, tal como incron (permite registar eventos quando um ficheiro é modificado) ou fswatch (uma ferramenta que também funciona em muitas outras variantes do Unix, usando o análogo de cada variante da inotificação do Linux).

179
179
179
2013-10-25 09:41:16 +0000

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

Torna muito fácil especificar vários ficheiros para ver (limitado apenas por ulimit -n), elimina o incómodo de lidar com ficheiros a serem substituídos e requer menos sintaxe bash:

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

Tenho-o utilizado em toda a minha árvore de fontes do projecto para executar os testes unitários do código que estou actualmente a modificar, e já tem sido um enorme impulso para o meu fluxo de trabalho.

Marcas como -c (limpar o ecrã entre execuções) e -d (sair quando um novo ficheiro é adicionado a um directório monitorizado) adicionam ainda mais flexibilidade, por exemplo:

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

A partir do início de 2018 ainda está em desenvolvimento activo e pode ser encontrado em Debian & Ubuntu (apt install entr); a construção a partir do repo do autor era indolor em qualquer caso.

112
112
112
2011-06-30 13:34:28 +0000

Eu escrevi um programa Python para fazer exactamente isto chamado quando-change .

A utilização é simples:

when-changed FILE COMMAND...

Ou para ver vários ficheiros:

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

FILE pode ser um directório. Ver recursivamente com -r. Use %f para passar o nome do ficheiro para o comando.

53
53
53
2013-08-20 17:12:47 +0000

Que tal este guião? Utiliza o comando stat para obter o tempo de acesso a um ficheiro e executa um comando sempre que há uma alteração no tempo de acesso (sempre que o ficheiro é acedido).

#!/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
30
30
30
2010-08-27 20:12:25 +0000

Solução usando Vim:

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

Mas eu não quero esta solução porque é um pouco irritante de digitar, é um pouco difícil lembrar o que digitar, exactamente, e é um pouco difícil desfazer os seus efeitos (necessidade de correr :au! BufWritePost myfile.py). Além disso, esta solução bloqueia o Vim até o comando terminar de executar.

Adicionei esta solução aqui apenas para completar, pois pode ajudar outras pessoas.

Para mostrar a saída do programa (e perturbar completamente o seu fluxo de edição, pois a saída irá escrever por cima do seu editor durante alguns segundos, até pressionar Enter), remova o comando :silent.

23
23
23
2012-06-09 23:51:16 +0000

Se tiver npm instalado, nodemon é provavelmente a forma mais fácil de começar, especialmente no OS X, que aparentemente não tem ferramentas de inotificação. Ele suporta a execução de um comando quando uma pasta muda.

21
21
21
2016-03-22 14:57:03 +0000

Para aqueles que não conseguem instalar inotify-tools como eu, isto deve ser útil:

watch -d -t -g ls -lR

Este comando sairá quando a saída mudar, ls -lR listará cada ficheiro e directório com o seu tamanho e datas, por isso se um ficheiro for alterado deve sair do comando, como diz o homem:

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

Eu sei que esta resposta pode não ser lida por ninguém, mas espero que alguém a alcance.

Exemplo de linha de comando:

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

Abrir outro terminal:

~ $ echo "testing" > /tmp/test

Agora o primeiro terminal vai sair 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}
17
17
17
2015-09-09 21:49:14 +0000

rerun2 no github ) é um script Bash de 10 linhas do formulário:

#!/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

Guarde a versão github como ‘rerun’ no seu PATH, e invoque-o usando:

rerun COMMAND

Corre COMMAND sempre que há um evento de modificação do sistema de ficheiros dentro do seu directório actual (recursivo. )

Coisas que se podem gostar:

  • Utiliza inotify, por isso é mais responsivo do que a sondagem. Fabuloso para correr testes de unidades de sub-milissegundo, ou renderizar ficheiros de pontos do graphviz, cada vez que se carrega em ‘save’.
  • Por ser tão rápido, não tem de se preocupar em dizer-lhe para ignorar grandes subdirs (como os módulos de nó_) apenas por razões de performance.
  • É extra super responsivo, porque só chama inotifywait uma vez, na inicialização, em vez de executá-lo, e incorrendo no caro hit de estabelecer relógios, em cada iteração.
  • São apenas 12 linhas de Bash
  • Porque é Bash, ele interpreta comandos que você passa exatamente como se você os tivesse digitado em um prompt Bash. (Provavelmente isto é menos fixe se usar outra shell.)
  • Não perde eventos que acontecem enquanto o COMMAND está a executar, ao contrário da maioria das outras soluções de inotificação nesta página.
  • No primeiro evento, entra num ‘período morto’ durante 0,15 segundos, durante o qual outros eventos são ignorados, antes do COMMAND ser executado exactamente uma vez. Isto é para que o fluxo de eventos causados pela dança “create-write-move” que Vi ou Emacs fazem ao salvar um buffer não cause múltiplas execuções laboriosas de um conjunto de testes possivelmente de execução lenta. Quaisquer eventos que ocorram enquanto o COMMAND está a executar não são ignorados - eles causarão um segundo período morto e subsequente execução.

Coisas que podem não gostar:

  • Ele usa inotify, por isso não funcionará fora da Linuxland.
  • Porque ele usa inotify, ele irá vomitar ao tentar ver directórios contendo mais ficheiros do que o número máximo de relógios inotify do utilizador. Por defeito, isto parece estar definido para cerca de 5.000 a 8.000 em máquinas diferentes que eu uso, mas é fácil de aumentar. Ver https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-reached
  • Não executa comandos contendo alias Bash. Eu poderia jurar que isto costumava funcionar. Em princípio, porque este é o Bash, não executando COMMAND numa subcapa, eu esperaria que isto funcionasse. Adoraria ouvir se alguém souber porque é que não funciona. Muitas das outras soluções desta página também não conseguem executar tais comandos.
  • Pessoalmente gostaria de poder carregar numa tecla do terminal em que está a correr para provocar manualmente uma execução extra de COMANDO. Posso acrescentar isto de alguma forma, simplesmente? Um laço “enquanto lê -n1” que também chama execução?
  • Neste momento codifiquei-o para limpar o terminal e imprimir o COMMAND executado em cada iteração. Algumas pessoas podem querer adicionar bandeiras de linha de comando para desligar coisas como esta, etc. Mas isto aumentaria muito o tamanho e a complexidade.

Este é um refinamento do @cychoi’s anwer.

12
12
12
2010-08-27 21:23:29 +0000

Aqui está uma simples concha Bourne shell script que:

  1. Requer dois argumentos: o ficheiro a monitorizar e um comando (com argumentos, se necessário)
  2. Copia o ficheiro que está a monitorizar para o directório /tmp
  3. Verifica a cada dois segundos se o ficheiro que está a monitorizar é 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-se a si próprio quando prime Ctr-C

Isto funciona no FreeBSD. O único problema de portabilidade que me ocorre é se algum outro Unix não tiver o comando mktemp(1), mas nesse caso você pode simplesmente codificar o nome do arquivo temporário.

8
8
8
2014-08-08 22:20:09 +0000

se tem nodemon instalado, então pode fazer isto:

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

No meu caso eu edito html localmente e envio-o para o meu servidor remoto quando um ficheiro muda.

nodemon -w <watch directory> -x "scp filename jaym@jay-remote.com:/var/www" -e ".html"
8
8
8
2010-08-27 20:12:59 +0000

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

6
6
6
2014-07-09 13:16:10 +0000

Veja em Guarda, em particular com este plugin: https://github.com/hawx/guard-shell

Pode configurá-lo para observar qualquer número de padrões no directório do seu projecto, e executar comandos quando ocorrem alterações. Há boas hipóteses mesmo que haja um plugin disponível para isso, o que está a tentar fazer em primeiro lugar.

6
6
6
2014-01-22 14:55:46 +0000

Outra solução com NodeJs, * fsmonitor ** :

  1. Instalar

  2. Em linha de comando* (exemplo, registos de monitorização e “retalho” se um ficheiro de registo for alterado)

5
5
5
2015-12-28 10:44:18 +0000
  • Watchdog *** é um projecto Python, e pode ser exactamente o que procura:

Plataformas suportadas

  • Linux 2. 6 (inotify)
  • Mac OS X (FSEvents, kqueue)
  • FreeBSD/BSD (kqueue)
  • Windows (ReadDirectoryChangesW com portas de conclusão I/O; ReadDirectoryChangesW worker threads)
  • OS-independent (pesquisar o disco para obter snapshots de directórios e compará-los periodicamente; lento e não recomendado)

Apenas escreveu um wrapper de linha de comando para ele * watchdog_exec ***:

Exemplo executa

Em eventos fs envolvendo arquivos e pastas no diretório atual, execute o comando echo $src $dst, a menos que o evento fs seja modificado, então execute o comando python $src.

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

Usando pequenos argumentos, e restringindo a execução apenas quando os eventos envolvem “ main.py”:

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

EDIT: Acabou de encontrar que o Watchdog tem um CLI oficial chamado watchmedo, por isso verifique isso também.

5
5
5
2012-07-02 16:36:51 +0000

Sob Linux:

man watch

watch -n 2 your_command_to_run

Correrá o comando a cada 2 segundos.

Se o seu comando demorar mais de 2 segundos a correr, o relógio vai esperar até que esteja pronto antes de o fazer novamente.

4
4
4
2010-08-27 20:37:52 +0000

Se o seu programa gera algum tipo de log/output, pode criar um Makefile com uma regra para esse log/output que depende do seu script e fazer algo como

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

Alternativamente, pode criar um alvo falso e ter a regra para ele tanto chamar o seu script como tocar no alvo falso (enquanto ainda depende do seu script).

4
4
4
2014-09-15 23:24:58 +0000
4
4
4
2015-06-17 18:03:51 +0000

Melhorado em Gilles’s answer .

Esta versão corre inotifywait uma vez e monitoriza os acontecimentos (por exemplo: modify) posteriormente. De tal forma que o inotifywait _ não_ necessita de ser reexecutado em cada evento encontrado.

É rápido e rápido!(mesmo quando se monitoriza recursivamente grandes directórios)

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
3
3
3
2010-08-27 20:05:59 +0000

Um pouco mais no lado da programação, mas você quer algo como inotify . Existem implementações em várias linguagens, tais como jnotify e pyinotify .

Esta biblioteca permite-lhe monitorizar ficheiros individuais ou directórios inteiros, e retorna eventos quando uma acção é descoberta. A informação devolvida inclui o nome do ficheiro, a acção (criar, modificar, renomear, apagar) e o caminho do ficheiro, entre outras informações úteis.

3
3
3
2012-11-11 21:48:31 +0000

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

/usr/ports/sysutils/wait_on
3
3
3
2014-04-29 13:56:13 +0000

Gosto da simplicidade do while inotifywait ...; do ...; done no entanto tem dois problemas:

  • Alterações de ficheiros que acontecem durante o do ...; vão ser perdidas
  • Slow quando usado em modo recursivo

Então fiz um helper script que usa inotifywait sem essas limitações: inotifyexec

Sugiro que coloque este script no seu caminho, como em ~/bin/. A utilização é descrita apenas ao correr o comando.

Exemplo: inotifyexec "echo test" -r .

3
3
3
2016-04-12 14:53:44 +0000

Melhorado solução de Sebastian com comando watch:

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 cuidado: o comando watch tem bugs conhecidos (ver homem): reage apenas a alterações no VISIBLE em partes terminais da saída do -g CMD.

2
2
2
2017-03-01 20:14:14 +0000

Pode tentar reflex .

Reflex é uma pequena ferramenta para ver uma directoria e voltar a executar um comando quando certos ficheiros mudam. É excelente para executar automaticamente tarefas de compilação/lint/teste e para recarregar a sua aplicação quando o código muda.

# Rerun make whenever a .c file changes
reflex -r '\.c$' make
1
1
1
2017-04-05 07:38:43 +0000

Tenho um GIST para isto e a utilização é bastante simples

watchfiles <cmd> <paths...>

https://gist.github.com/thiagoh/5d8f53bfb64985b94e5bc8b3844dba55

1
1
1
2017-06-03 12:10:57 +0000

Como alguns outros já fizeram, também escrevi uma ferramenta de linha de comando leve para o fazer. Está totalmente documentada, testada e modular.

Watch-Do

Instalação

Pode instalá-lo (se tiver Python3 e pip) usando:

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

Utilização

Use-o de imediato ao correr:

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

Características

  • Suporta file globbing (use -w '*.py' ou -w '**/*.py')
  • Executa comandos múltiplos numa mudança de ficheiro (basta especificar a bandeira -d novamente)
  • Mantém dinamicamente a lista de ficheiros para ver se o globbing é usado (-r para ligar isto)
  • Múltiplas formas de “ver” um ficheiro:
  • Tempo de modificação (padrão)
  • Hash de arquivo
  • Trivial para implementar o seu próprio arquivo (este é o ModificationTime watcher)
  • Desenho modular. Se quiser que os comandos sejam executados, quando um ficheiro é acedido, é trivial escrever o seu próprio observador (mecanismo que determina se os executantes devem ser executados).
1
1
1
2016-02-26 19:10:05 +0000

Eu utilizo este guião para o fazer. Estou usando inotify no monitor-mode

#!/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

Guarde isto como runatwrite.sh

Usage: runatwrite.sh myfile.sh

ele irá correr o myfile.sh a cada escrita.

1
1
1
2015-09-09 19:36:04 +0000

Escrevi um programa Python para fazer exactamente isto, chamado rerun .

UPDATE: Esta resposta é um script Python que faz sondagens para alterações, o que é útil em algumas circunstâncias. Para um script Bash apenas para Linux que usa inotify, veja a minha outra resposta, procure nesta página por ‘rerun2’.

Instalar para Python2 ou Python3 com:

pip install --user rerun

e o uso é muito simples:

rerun "COMMAND"

O comando é esperado como um único arg, e não como uma sequência de args separados por espaço. Por isso cite-o como mostrado, o que reduz qualquer fuga extra que teria de adicionar. Basta digitar o comando como o teria digitado na linha de comando, mas rodeado de aspas.

Por defeito, observa todos os ficheiros dentro ou sob o directório actual, saltando coisas como dirs de controlo de origem conhecidos, .git, .svn, etc.

Os sinalizadores opcionais incluem ‘-i NAME’ que ignora alterações a ficheiros ou directórios nomeados. Isto pode ser dado várias vezes.

Como é um script Python, precisa de correr o comando como um subprocesso, e nós usamos uma nova instância da actual shell do utilizador para interpretar ‘COMMAND’ e decidir qual o processo a correr de facto. No entanto, se o seu comando contém alias shell e similares definidas em .bashrc, estas não serão carregadas pela sub-construção. Para corrigir esta situação, pode dar um sinal ‘-I’, para usar sub-camadas interactivas (também conhecidas como ‘login’). Isto é mais lento e mais sujeito a erros do que iniciar uma shell normal, porque tem de obter o seu .bashrc.

Eu utilizo-o com o Python 3, mas por último verifiquei que a repetição ainda funciona com o Python 2.

Espada de dois gumes é que utiliza a sondagem em vez da inotificação. No lado positivo, isto significa que funciona em todos os sistemas operativos. Além disso, é melhor do que algumas outras soluções mostradas aqui em termos de apenas executar o comando dado uma vez para um monte de alterações no sistema de ficheiros, não uma vez por ficheiro modificado, enquanto ao mesmo tempo executa o comando uma segunda vez se algum ficheiro mudar novamente enquanto o comando está a correr.

No lado negativo, a sondagem significa que existe uma latência de 0.0 a 1.0 segundo, e claro que é lento a monitorizar directórios extremamente grandes. Dito isto, nunca encontrei um projecto suficientemente grande para que isto seja sequer perceptível desde que se use ‘-i’ para ignorar coisas grandes como o seu virtualenv e node_modules.

Hmmm. rerun tem sido indispensável para mim durante anos - basicamente utilizo-o oito horas todos os dias para executar testes, reconstruir ficheiros de pontos enquanto os edito, etc. Mas agora venho digitar isto aqui, é claro que preciso de mudar para uma solução que usa inotify (já não uso Windows ou OSX.) e está escrito em Bash (por isso funciona com pseudónimos sem qualquer manipulação extra).

1
1
1
2015-09-01 04:53:52 +0000

Uma resposta oneliner que estou a utilizar para acompanhar uma mudança de ficheiro:

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

Não é necessário inicializar BF se souber que a primeira data é a hora de início.

Isto é simples e portátil. Existe outra resposta baseada na mesma estratégia usando um script aqui. Veja também.


Utilização: Estou a usar isto para depurar e manter um olho no ~/.kde/share/config/plasma-desktop-appletsrc; que por alguma razão desconhecida continua a perder o meu SwitchTabsOnHover=false

1
1
1
2016-03-22 15:33:56 +0000

Para quem usa o OS X, você pode usar um LaunchAgent para observar um caminho/arquivo para mudanças e fazer algo quando isso acontecer. FYI - LaunchControl é uma boa aplicação para facilmente fazer/modificar/remove daemons/agentes.

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
0
0
2019-11-26 15:53:22 +0000

Se não quiser instalar nada de novo para isto, aqui está um pequeno script da shell que pode colocar no seu caminho (por exemplo, em $HOME/bin). Ele executa um comando quando um ou mais ficheiros fornecidos são alterados. Por exemplo:

$ onchange './build' *.txt
#!/bin/sh
cmd="$1"; shift
files="$@"
changed() { tar -c $files | md5sum; } # for on every save use: `stat -c %Z $files`
while true; do
  if ["$(changed)" != "$last"]; then
    last="$(changed)"
    $cmd
  fi
  sleep 1
done

Ele tars, e depois hashes o conteúdo dos ficheiros e/ou directórios, para que não corra cada vez que acertar compulsivamente no CTRL-S (ou digite :w), mas apenas uma vez que algo realmente mude. Note que irá verificar a cada segundo, por isso não inclua muito ou a sua máquina pode ficar lenta. Se quiser que funcione em cada save, use o stat em vez dele (ver comentário). Também, para mac md5sum chama-se md5 se bem me lembro.

Pequeno truque: No momento em que o quiser usar, provavelmente vai querer repetir o último comando que acabou de correr, mas repetidamente. Pode usar o atalho !! para ‘injectar’ o último comando neste:

$ onchange "!!" *.txt
0
0
0
2018-04-12 18:32:28 +0000

Descrição

Esta acção irá observar um ficheiro para alterações e executar qualquer comando (incluindo outros argumentos) foi dada como segunda declaração.* Irá também limpar o ecrã e imprimir a hora da última execução. Nota: pode tornar a função mais (ou menos) reactiva alterando o número de segundos em que a função deve dormir após cada ciclo de laço.

Exemplo de utilização

watch_file my_file.php php my_file.php

Esta linha observará um ficheiro php my_file.php e correrá através do interpretador php sempre que mudar.

Definição da função

function watch_file (){

### Set initial time of file
LTIME=`stat -c %Z $1`
printf "&00133c"
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 "&00133c"
    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.

0
0
0
2018-03-19 20:30:21 +0000

Uso básico

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

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

Este comando sai sob as seguintes condições:

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

Você diz que está a usar o vim, e o vim irá substituir o ficheiro no save. Eu testei isto funciona com o vim.

Pode ignorar a saída deste comando, ele pode mencionar algo como:

cauda: ‘myfile.txt’ foi substituído; após o fim do novo ficheiro

Uso avançado

Pode combinar isto com timeout para devolver verdadeiro ou falso. Pode utilizá-lo assim:

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

Discussão

tail utiliza inotify debaixo da capota. É assim que se obtém este comportamento assíncrono extravagante sem qualquer votação. Provavelmente existe algum outro programa padrão unix que usa o inotify que podemos abusar de forma mais elegante.

Por vezes estes comandos saem imediatamente mas se os executar imediatamente uma segunda vez então funcionam como anunciado. Eu cometi um erro algures, por favor ajude-me a corrigir isto.

No RHEL posso usar:

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

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

0
0
0
2019-01-28 16:51:42 +0000

find pode fazer o truque.

while true; do
    find /path/to/watched/file -ctime 1s | xargs do-what-you-will
done

find -ctime 1s imprime o nome do ficheiro se este foi alterado no último 1s.

0
0
0
2015-05-13 15:21:33 +0000

Para pessoas que encontram isto no Googling para alterações a um ficheiro particular, a resposta é muito mais simples (inspirada pela resposta de Gilles ).

Se quiser fazer algo após um determinado ficheiro ter sido escrito, eis 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

Guarde isto como, por exemplo, copy_myfile.sh e coloque o ficheiro .sh na pasta /etc/init.d/ para que seja executado no arranque.

0
0
0
2019-03-21 11:33:37 +0000

Tive uma situação ligeiramente diferente. Mas sinto que isto pode ser útil para alguém que leia esta pergunta.

Eu precisava de ser notificado quando um ficheiro de registo mudava de tamanho, mas não era necessário imediatamente. E poderia ser dias ou semanas no futuro, por isso não podia usar o inotify (que não estava instalado/activado naquele servidor de qualquer forma) na linha de comando (não queria usar o nohup ou similar). Então decidi correr um script bash em cron para verificar

O script escreve o tamanho do ficheiro observado num ficheiro de texto e em cada cron corre e verifica, se esse valor mudou e envia-me a última linha por correio se o

#!/bin/bash
FILE_TO_WATCH="/path/to/log_file.log"
FILESIZE_FILE="/path_to/record.txt"
SUBJECT="Log file 'log_file.log' has changed"
MAILTO="info@example.com"
BODY="Last line of log file:\n"
LAST_LINES=1

# get old recorded file size from file
OLD_FILESIZE=$(cat "${FILESIZE_FILE}")
# write current file size into file
stat --printf="%s" "${FILE_TO_WATCH}" > "${FILESIZE_FILE}"
# get new recorded file size from file
NEW_FILESIZE=$(cat "${FILESIZE_FILE}")

if ["${OLD_FILESIZE}" != "${NEW_FILESIZE}"]; then
    echo -e "${BODY}"$(tail -${LAST_LINES} ${FILE_TO_WATCH}) | mail -s "${SUBJECT}" "${MAILTO}"
fi
``` foi alterado.
0
0
0
2019-11-23 23:41:53 +0000

Confira https://github.com/watchexec/watchexec .

watchexec é uma ferramenta simples e autônoma que observa um caminho e executa um comando sempre que detecta modificações.

Exemplo

Veja todos os arquivos JavaScript, CSS e HTML no diretório atual e todos os subdiretórios para alterações, executando make quando uma alteração é detectada:

$ watchexec --exts js,css,html make

0
0
0
2017-03-20 13:52:53 +0000

A ferramenta “fido” pode ser mais uma opção para esta necessidade. Ver https://www.joedog.org/fido-home/