linux – ファイルが変更されたときにコマンドを実行するには?

linux script shell unix

ファイルが変更されたときにコマンドを実行するための、迅速でシンプルな方法が欲しい。非常にシンプルで、ターミナル上で実行したままにしておいて、そのファイルの作業が終わったらいつでもそれを閉じられるようなものが欲しいのです

現在はこれを使っています

while read; do ./myfile.py ; done

そして、そのファイルをエディタで保存するときには、そのターミナルに行って、Enterを押す必要があります。私が欲しいのは、次のようなものです

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

あるいは、それと同じくらい簡単な他の解決策も

BTW: 私はVimを使っていて、BufWrite上で何かを実行するためのオートコマンドを追加できることは知っていますが、これは私が今望んでいるような解決策ではありません

更新:できれば捨てられるシンプルなものが欲しい。さらに、プログラムの出力が見たい(エラーメッセージが見たい)ので、ターミナルで実行できるものが欲しい

回答について。どの回答もありがとうございました!どれもとても良いものばかりで、それぞれのアプローチの仕方が全く違います。私は1つだけ受け止めなければならないので、一番上品ではないとわかっていても、実際に使ってみたもの(シンプルで早くて覚えやすかった)を受け止めています

  497  None  2010-08-27


ベストアンサー

inotifywait (ディストリビューションのinotify-toolsパッケージをインストールする) を使って、シンプルに

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

or

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

最初のスニペットはよりシンプルですが、大きな欠点があります: inotifywaitが実行されていない間(特にmyfileが実行されている間)に実行された変更を見逃してしまいます。二番目のスニペットにはこのような欠陥はありません。しかし、ファイル名に空白が含まれていないことを前提としていることに注意してください。もしそれが問題ならば、--formatオプションを使って、ファイル名を含まないように出力を変更してください

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

どちらにしても、制限があります: あるプログラムが既存のmyfileに書き込むのではなく、myfile.pyを別のファイルに置き換えた場合、inotifywaitは死んでしまいます。多くのエディタはこのように動作します

この制限を克服するには、ディレクトリにinotifywaitを使用します

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

あるいは、incron (ファイルが変更されたときにイベントを登録することができます) や fswatch (他の多くの Unix バリアントでも動作するツールで、Linux の inotify の各バリアントのアナログを使っています) のような、同じ基本的な機能を使っている別のツールを使ってもよいでしょう

468  Gilles ‘SO- stop being evil’  2010-08-27


entr (http://entrproject.org/) は inotify のよりフレンドリーなインターフェイスを提供します (また *BSD & Mac OS X もサポートしています)

これにより、監視する複数のファイルを非常に簡単に指定することができます (ulimit -nのみに制限されます)

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

プロジェクト全体のソースツリー上で、現在修正中のコードのユニットテストを実行するために使用していますが、すでに私のワークフローを大きく後押ししてくれています

-c (実行中に画面をクリアする) や -d (監視対象のディレクトリに新しいファイルが追加された場合に終了する) などのフラグをつけることで、さらに柔軟性が増します

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

2018年初頭の時点ではまだ活発な開発が行われており、Debian & Ubuntu (apt install entr); 作者のレポからのビルドはいずれにしても痛みを伴うことはありませんでした

203  Paul Fenney  2013-10-25


私はwhen-changedというPythonプログラムを書きました

使い方は簡単です

when-changed FILE COMMAND...

複数のファイルを見ることも

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

FILEにはディレクトリを指定することができます。-rで再帰的にウォッチする。コマンドにファイル名を渡すには %f を使う

122  joh  2011-06-30


このスクリプトはどうでしょうか。statコマンドを使ってファイルのアクセス時間を取得し、アクセス時間に変化があったとき(ファイルにアクセスするたび)にコマンドを実行します

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

57  VDR  2013-08-20


Vimを使ったソリューション

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

しかし、この解決策は、入力するのがめんどくさいし、何を入力すればいいのか正確に覚えるのがちょっと大変だし、その効果を元に戻すのがちょっと難しい(:au! BufWritePost myfile.pyを実行する必要がある)ので、私はこの解決策を望んでいません。さらに、この解決策はコマンドの実行が終了するまで Vim をブロックします

他の人の助けになるかもしれないので、この解決策をここに追加しました

プログラムの出力を表示するには(Enterを押すまでの数秒間、出力がエディタの上に書き込まれるので、編集の流れを完全に混乱させます)、:silentコマンドを削除してください

31  Denilson Sá Maia  2010-08-27


たまたま npm がインストールされているのであれば、nodemon を使うのが一番簡単な方法でしょう。フォルダが変更されたときのコマンド実行をサポートしています

26  davidtbernal  2012-06-09


私のようにinotify-toolsをインストールできない人には、これが役に立つはずです

watch -d -t -g ls -lR

このコマンドは出力が変更された場合に終了します、ls -lRはすべてのファイルとディレクトリをそのサイズと日付でリストアップしますので、ファイルが変更された場合には、manが言うようにコマンドを終了する必要があります

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

この答えは誰にも読まれないかもしれませんが、誰かが手を伸ばしてくれることを願っています

コマンドラインの例

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

別の端末を開く

~ $ echo "testing" > /tmp/test

これで、最初の端末は1,2,3を出力します

簡単なスクリプトの例です

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

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

26  Sebastian  2016-03-22


rerun2 (on github) は 10 行の Bash スクリプトです

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

github のバージョンを ‘rerun’ として PATH に保存し、以下のようにして起動します

rerun COMMAND

カレントディレクトリ内でファイルシステムの変更イベントが発生するたびにcommandを実行します(再帰的に)

気に入るかもしれないこと

  • inotify を使用しているので、ポーリングよりも反応が良いです。サブミリ秒単位のユニットテストを実行したり、’save’ を押すたびに graphviz ドットファイルをレンダリングしたりするのに最適です
  • 非常に高速なので、パフォーマンスの理由だけで大きなサブディレクトリ(node_modulesのような)を無視するようにわざわざ指示する必要はありません
  • 起動時に一度だけ inotifywait を呼び出すだけなので、起動時の反応が非常に良く、繰り返しのたびに時計を確立するための高価なヒットが発生することはありません
  • バッシュの12行だけです
  • Bash なので、あなたが渡したコマンドを Bash プロンプトで入力したかのように正確に解釈します。(おそらく他のシェルを使っている場合は、これはあまりクールではないでしょう)
  • このページの他の多くのinotifyソリューションとは異なり、COMMAND実行中に発生するイベントを失うことはありません
  • 最初のイベントでは、0.15 秒間の「デッドピリオド」に入り、その間は他のイベントは無視されます。これは、バッファを保存する際にViやEmacsが行う「作成・書き込み・移動」のダンスによって引き起こされるイベントが、実行速度が遅くなる可能性のあるテストスイートを何度も実行しないようにするためです。COMMAND の実行中に発生したイベントは無視されません

嫌いになりそうなこと

  • inotify を使用しているため、Linuxland 以外では動作しません
  • inotify を使用しているため、inotify が監視するユーザーの最大数以上のファイルを含むディレクトリを監視しようとすると嘔吐します。デフォルトでは、私が使用している異なるマシンでは 5,000 から 8,000 程度に設定されているようですが、これは簡単に増やすことができます。https://unix.stackexchange.com/questions/13751/kernel-inotify-watch-limit-reached を参照してください
  • Bash エイリアスを含むコマンドの実行に失敗します。以前はこれが機能していたと断言できます。原則として、これは Bash であり、サブシェルで COMMAND を実行しているわけではないので、これが動作することを期待しています。なぜ動かないのか知っている人がいたら教えてほしいです。このページの他のソリューションの多くも、このようなコマンドを実行することができません
  • 個人的には、実行中のターミナルでキーを押すことで、手動でCOMMANDの追加実行ができたらいいなと思います。これをどうにかして簡単に追加できないでしょうか?同時に実行されている’while read -n1’ループでexecuteを呼び出すように?
  • 今のところ、私はターミナルをクリアして、実行されたcommandを反復するたびに表示するようにコード化しています。このようなことをオフにするためにコマンドラインフラグを追加したい人もいるかもしれません。しかし、これはサイズと複雑さを何倍にも増やすことになります

これは@cychoiさんのアンサーを洗練させたものです

20  Jonathan Hartley  2015-09-09


ここに簡単なシェル・ボーンのシェル・スクリプトがあります

  1. 2つの引数を取ります: 監視するファイルとコマンド (必要に応じて引数付き)
  2. 監視しているファイルを/tmpディレクトリにコピーします
  3. 監視しているファイルがコピーより新しいかどうかを2秒ごとにチェックします
  4. 新しいものであれば、コピーを新しいオリジナルで上書きしてコマンドを実行します
  5. 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
    

これは FreeBSD で動作します。私が考えられる唯一の移植性の問題は、他の Unix が mktemp(1) コマンドを持っていない場合ですが、その場合はテンポラリファイル名をハードコードするだけです

13  MikeyMike  2010-08-27


nodemonがインストールされていれば、このようにすることができます

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

私の場合は、ローカルでhtmlを編集して、ファイルが変更されたときにリモートサーバーに送信しています

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

10  Jay  2014-08-08


incronを見てみてください。これはcronに似ていますが、時間の代わりにinotifyイベントを使用します

9  Florian Diesch  2010-08-27


NodeJsを使ったもう一つの解決策は、fsmonitor

  1. Install

    sudo npm install -g fsmonitor
    
  2. コマンドラインから(例:ログを監視し、1つのログファイルが変更された場合は「小売」)

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

8  Atika  2014-01-22


特にこのプラグインでGuardを調べてみてください

GitHub - hawx/guard-shell: Guard::Shell automatically runs shell commands when watched files are modified.
Guard::Shell automatically runs shell commands when watched files are modified. - hawx/guard-shell

あなたのプロジェクトのディレクトリ内の任意の数のパターンを監視し、変更が発生したときにコマンドを実行するように設定することができます。そもそもあなたがやろうとしていることに対応したプラグインが用意されている可能性も十分にあります

7  Wouter Van Vliet  2014-07-09


Under Linux:

man watch

watch -n 2 your_command_to_run

2秒ごとにコマンドを実行します

コマンドを実行するのに2秒以上かかる場合、ウォッチは実行が終わるまで待ってから再度実行します

6  Eric Leschinski  2012-07-02


WatchdogはPythonのプロジェクトで、あなたが探しているものと同じかもしれません

Supported platforms

  • Linux 2.6 (inotify)
  • Mac OS X(FSEvents、kqueue)
  • FreeBSD/BSD (kqueue)
  • Windows(I/O補完ポートを持つReadDirectoryChangesW; ReadDirectoryChangesWワーカースレッド)
  • OS に依存しない (ディレクトリのスナップショットをディスクにポーリングし、定期的に比較します。)

そのためのコマンドラインラッパー watchdog_exec を書いただけです

Example runs

カレントディレクトリ内のファイルやフォルダを含むfsイベントでは、fsイベントが変更されていない限り、echo $src $dstコマンドを実行し、python $srcコマンドを実行する

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

短い引数を使用し、イベントに “main.py” が含まれている場合のみ実行するように制限しています

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

EDIT: Watchdogにはwatchmedoという公式のCLIがあることを発見したので、そちらもチェックしてみてください

6  Samuel Marks  2015-12-28


プログラムが何らかのログ/出力を生成している場合は、スクリプトに依存するログ/出力のルールを持つMakefileを作成して、次のようにします

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

別の方法として、偽のターゲットを作成して、そのルールでスクリプトを呼び出し、偽のターゲットに触れるようにすることもできます (スクリプトに依存したまま)

5  ctgPi  2010-08-27


swarminglogicwatchfile.sh というスクリプトを書き、GitHub Gist としても利用可能です

5  Denilson Sá Maia  2014-09-15


ジルの回答を改良しました

このバージョンでは、inotifywaitを一度だけ実行し、その後のイベント(: modify)を監視します。このようにして、inotifywait はイベントが発生するたびに再実行する必要がありません

早くて速い!(大規模なディレクトリを再帰的に監視する場合でも)

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

5  cychoi  2015-06-17


もう少しプログラミング側の話になりますが、inotifyのようなものが欲しくなります。jnotifypyinotify など、多くの言語で実装されています

このライブラリを使用すると、単一のファイルまたはディレクトリ全体を監視することができ、アクションが発見されるとイベントを返します。返される情報には、ファイル名、アクション (作成、変更、リネーム、削除)、ファイルパスなどの有用な情報が含まれます

4  John T  2010-08-27


FreeBSDをお探しの方のために、こちらの移植版をご紹介します

/usr/ports/sysutils/wait_on

4  akond  2012-11-11


私はwhile inotifywait ...; do ...; doneのシンプルさが好きですが、2つの問題があります

  • do ...;の間に起こったファイルの変更は見逃されます
  • 再帰モードで使用すると遅い

そこで、これらの制約を受けずにinotifywaitを利用するヘルパースクリプトを作ってみました。 inotifyexec です

このスクリプトを~/bin/のようにパスに入れておくといいと思います。使い方はコマンドを実行するだけで説明します

例としては、以下のようになります。inotifyexec "echo test" -r .

4  Wernight  2014-04-29


セバスチャンの解決策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

Call example:

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

動作しますが、注意が必要です。watch コマンドには既知のバグがあります (man を参照してください): -g CMD 出力のターミナル部分の VISIBLE の変更のみに反応します

4  alex_1948511  2016-04-12


あなたはreflexを試してみてください

Reflex はディレクトリを監視し、特定のファイルが変更されたときにコマンドを再実行するための小さなツールです。コンパイル/リント/テストタスクを自動的に実行したり、コードが変更されたときにアプリケーションをリロードしたりするのに最適です

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

3  masterxilo  2017-03-01


ファイル変更の記録を残すために使っているオネリ回答

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

最初の日付が開始時刻だと分かっていればBFの初期化は不要です

これはシンプルで携帯性に優れています。ここにスクリプトを使った同じ戦略に基づく別の答えがあります。こちらもご覧ください


使用法。何らかの理由で ~/.kde/share/config/plasma-desktop-appletsrc のデバッグと監視に使用しています

2  DrBeco  2015-09-01


私はこのスクリプトを使っています。モニターモードでイノティファイを使っています

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

これを runatwrite.sh として保存します

Usage: runatwrite.sh myfile.sh

書き込みのたびに myfile.sh を実行します

2  Fernando Silva  2016-02-26


OS Xを使っている人は、LaunchAgentを使ってパスやファイルの変更を監視し、変更が起こったときに何かをすることができます。参考までに – LaunchControlは簡単にデーモンやエージェントを作成、変更、削除するのに良いアプリです

(ここから引用した例)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN
Page Not Found - Apple
Apple info, Mac, iPhone, iPad, Apple Watch, and more. Find your way around apple.com.
<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>

2  Hefewe1zen  2016-03-22


これのためにGISTを持っているのですが、使い方はかなり簡単です

watchfiles <cmd> <paths...>
watchfiles: Watch multiple files and execute bash commands as file changes occur
watchfiles: Watch multiple files and execute bash commands as file changes occur - watchfiles.md

2  thiagoh  2017-04-05


他の人がやっていたように、私もこれを行うための軽量なコマンドラインツールを書きました。これは完全に文書化され、テストされ、モジュール化されています

Watch-Do

Installation

を使ってインストールします(Python3とpipがあれば)

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

Usage

走ってすぐに使う

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

Features Overview

  • ファイルのグロブをサポートしています(-w '*.py'または-w '**/*.py'を使用)
  • ファイルの変更に対して複数のコマンドを実行する(-dフラグを再度指定するだけ)
  • グロブが使用されている場合、監視するファイルのリストを動的に保持する (-r でオンにする)
  • ファイルを「見る」には複数の方法があります:
    • 変更時間(デフォルト)
    • File hash
    • 自分で実装するのは簡単です(これはModificationTimeウォッチャーです)
  • モジュラー設計。ファイルにアクセスしたときに、コマンドを実行させたいのであれば、自分でウォッチャー(実行すべきかどうかを判断する仕組み)を書くのは些細なことです

2  vimist  2017-06-03


まさにこれを行うためのPythonのプログラムを書いたのがrerunです

UPDATE: この回答は、変更をポーリングするPythonスクリプトで、状況によっては便利です。inotifyを使用するLinux専用のBashスクリプトについては、私の別の回答を参照してください

Python2、Python3用にインストールします

pip install --user rerun

と使い方はとてもシンプルです

rerun "COMMAND"

コマンドは、スペースで区切られた一連の引数ではなく、単一の引数として期待されています。そのため、表示されているように引用符で囲んでおくことで、追加しなければならない余分なエスケープを減らすことができます。コマンドラインでコマンドを入力するときと同じように、引用符で囲んで入力してください

デフォルトでは、カレントディレクトリ以下のすべてのファイルを監視し、既知のソース管理用ディレクトリ、.git、.svn などのようなものはスキップします

オプションのフラグとして、名前付きファイルやディレクトリへの変更を無視する ‘-i NAME’ があります。これは複数回指定することができます

これはPythonスクリプトなので、コマンドをサブプロセスとして実行する必要があり、ユーザーの現在のシェルの新しいインスタンスを使用して’COMMAND’を解釈し、実際に実行するプロセスを決定します。しかし、コマンドに.bashrcで定義されているシェルエイリアスなどが含まれている場合、これらはサブシェルによって読み込まれません。これを修正するには、再実行時に ‘-I’ フラグを指定して、対話型 (別名 ‘ログイン’ サブシェル) サブシェルを使用することができます。この方法は、通常のシェルを起動するよりも遅く、エラーが発生しやすいです

私はPython 3で使っていますが、最後に確認したところ、rerunはまだPython 2で動作していました

両刃の剣は、イノティファイの代わりにポーリングを使用していることです。良い面としては、これはすべての OS で動作することを意味します。さらに、ここで示した他のいくつかのソリューションよりも、変更されたファイルごとに一度だけではなく、ファイルシステムの変更の束に対して一度だけコマンドを実行するという点で優れています

欠点としては、ポーリングは 0.0~1.0 秒のレイテンシがあることを意味し、もちろん、非常に大きなディレクトリを監視するのは遅いです。とはいえ、virtualenv や node_modules のような大きなものを無視するために ‘-i’ を使用する限り、これが目立ってしまうほど大きなプロジェクトに遭遇したことはありません

うーん。rerun は何年も前から私にとってなくてはならないもので、基本的に毎日8時間はテストをしたり、ドットファイルを編集しながら再構築したりしています。しかし、今ここにこれを書きに来て、私は inotify を使ったソリューションに切り替える必要があることは明らかです(私はもう Windows や OSX は使っていません)

2  Jonathan Hartley  2015-09-09


特定のファイルへの変更をググって見つけた人にとっては、答えはもっとシンプルです(Gillesの答えに触発されています)

特定のファイルに書き込まれた後に何かをしたい場合は、次のようにします

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

これを例えばcopy_myfile.shとして保存し、.shファイルを/etc/init.d/フォルダに入れて起動時に実行させます

1  LondonRob  2015-05-13


タイトルとURLをコピーしました