2番目のinotifyイベントが発生した場合、プロセスを終了する方法は?

2番目のinotifyイベントが発生した場合、プロセスを終了する方法は?
inotifywait -q -m -e close_write,create --recursive ../orgmode-parse-print | 
while read -r filename event; do
    echo $filename;
    echo $event
    sleep infinity;
done

上記の問題は永遠に「スリープ」状態であり、決して終了しないということです。sleep他のイベントが発生した場合、プロセス(デフォルトではwhileループ(含む)の内容)を終了または再開するにはどうすればよいですか?

つまり、コマンドを実行して終了し(割り込みであると仮定)、ファイルが変更されたら再起動します。

sleepここではこれを例として使用します。実行中の実際のプロセスは長期実行プロセスです。

答え1

これは私にとって効果的です。

$ inotifywait -q -m -e close_write,create --recursive dir1/ | \
  ( 
    CNT=0; 
    while read -r filename event; do 
       echo "count: $CNT filename: $filename  event: $event"; ((CNT++)); 
       [ "$CNT" -eq 1 ] && exit; 
    done 
  )

はい

まず、サンプルディレクトリ構造を作成しました。

$ mkdir -p dir1/dir2/dir{3..5}

$ tree dir1/
dir1/
└── dir2
    ├── afile
    ├── dir3
    ├── dir4
    └── dir5

4 directories, 1 file

その後、これを実行してディレクトリの表示を開始します。

$ inotifywait -q -m -e close_write,create --recursive dir1/ | ( CNT=0; while read -r filename event; do echo "count: $CNT filename: $filename  event: $event"; ((CNT++)); [ "$CNT" -eq 1 ] && exit; done )

次に、touch afileこのディレクトリからコマンドを実行します。

$ cd dir1/dir2
$ touch afile
$ touch afile

結果は次のとおりですinotifywait

count: 0 filename: dir1/dir2/  event: CLOSE_WRITE,CLOSE afile

2番目の「イベント」に達すると終了します。

問題とより良い解決策

(...while ...)echoパイプにサブシェルを使用する場合の1つの問題は、2番目のイベントが発生したときに2番目のメッセージが表示されないことです。問題ありません。次のように簡単にリファクタリングできます。

$ CNT=0; while read -r filename event; do echo "count: $CNT filename: $filename  event: $event"; ((CNT++)); [ "$CNT" -eq 2 ] && break; done < <(inotifywait -q -m -e close_write,create --recursive dir1/)
count: 0 filename: dir1/dir2/  event: CLOSE_WRITE,CLOSE afile
count: 1 filename: dir1/dir2/  event: CLOSE_WRITE,CLOSE afile
$

拡張:

$ CNT=0; \
  while read -r filename event; do \
    echo "count: $CNT filename: $filename  event: $event"; ((CNT++)); \
    [ "$CNT" -eq 2 ] && break; \
  done < <(inotifywait -q -m -e close_write,create --recursive dir1/)
count: 0 filename: dir1/dir2/  event: CLOSE_WRITE,CLOSE afile
count: 1 filename: dir1/dir2/  event: CLOSE_WRITE,CLOSE afile
$

バックグラウンドタスクがあります

while ...ループ内でブロックされているアクションがある場合は、それらを導入して終了し、ループが処理できるようにtrapバックグラウンドに配置できます。while ...inotifywait

例:

$ cat ./notifier.bash
#!/bin/bash

trap 'kill $(jobs -p)' EXIT;

CNT=0
while read -r filename event; do
  sleep 1000 &
  echo "count: $CNT filename: $filename  event: $event"; ((CNT++))
  [ "$CNT" -eq 2 ] && break
done < <(inotifywait -q -m -e close_write,create --recursive dir1/)

実行中:

$ ./notifier.bash
count: 0 filename: dir1/dir2/  event: CLOSE_WRITE,CLOSE afile
count: 1 filename: dir1/dir2/  event: CLOSE_WRITE,CLOSE afile
./notifier.bash: line 1: kill: (30301) - No such process

sleepそしてバックグラウンドプロセスの残りの部分はありません。

$ ps -eaf|grep [s]leep
$

trap最後の調整について皆さんも気づいたでしょう。これにより、kill $(jobs -p)次のように画面にゴミが投げられます。時には次のようになります。

./notifier.bash: line 1: kill: (30301) - No such process

次のように整理できます。

 trap 'kill $(jobs -p) > /dev/null 2>&1' EXIT;

今実行すると:

$ ./notifier.bash
count: 0 filename: dir1/dir2/  event: CLOSE_WRITE,CLOSE afile
count: 1 filename: dir1/dir2/  event: CLOSE_WRITE,CLOSE afile
$

引用する

答え2

新しい割り込みを受け取ったときにコードが実行しているすべてのタスクを終了すると仮定すると、次のbashスクリプトがこの目的に適しています。

#!/bin/bash

my_function () {
   sleep infinity
}

declare -A cache
inotifywait -q -m -e close_write,create --recursive ../orgmode-parse-print |
while read -r filename event; do
    if [ "${cache[pid_my_function]}" ]; then kill "${cache[pid_my_function]}"; fi
    echo $filename
    echo $event
    my_function &
    cache[pid_my_function]=$!
done

デフォルトでは、スクリプトはsleep infinity関数が呼び出されたときに別のプロセスとして実行できるように、長いプロセス(とマークされています)を関数の内部に配置します&。このコマンドは、$!後で新しい割り込みが到着したときに終了できるように、変数にプロセス番号を出力します。


OBS:このスクリプトは、新しい割り込みを受け取ったときにコードが実行する操作を終了しますが、実際にそうしたいかどうかはわかりません。関数を使用すると、プロセスを終了せずに各割り込み呼び出しを別々のプロセスとして実行できるため、スクリプトがすべての割り込みで実行されて&いることを確認できます。

関連情報