web-dev-qa-db-ja.com

終了スクリプト-シェルスクリプトで開始された-f

以下があります。

  1. A Javaプロセスがログをstdoutに書き込む
  2. Javaプロセスを開始するシェルスクリプト
  3. 前のスクリプトを実行してログをリダイレクトする別のシェルスクリプト
  4. tail -fコマンドを使用して、ログファイルで成功メッセージを確認します。

コードに出口0があっても、tail -fプロセスを終了できません。

これでは、スクリプトを完了できません。バッシュでこれを行う他の方法はありますか?

コードは次のようになります。

function startServer() {
  touch logfile
  startJavaprocess > logfile &

  tail -f logfile | while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      exit 0
    fi
  done
}
27
rangalo

私が思いつくことができる最良の答えはこれです

  1. 読み取りにタイムアウトを設定しますtail -f logfile | read -t 30 line
  2. 末尾を--pid=$$で始めます。これにより、bashプロセスが終了したときに終了します。

それは私が考えることができるすべてのケースをカバーします(サーバーは出力なしでハングし、サーバーは終了し、サーバーは正しく起動します)。

サーバーの前でテールを開始することを忘れないでください。

tail -n0 -F logfile 2>/dev/null | while read -t 30 line

-Fは、ファイルが存在しない場合でもファイルを「読み取り」ます(表示されたときに読み取りを開始します)。 -n0はファイル内の既存の内容を読み取らないため、毎回上書きするのではなく、ログファイルに追加して、標準のログローテーションを行うことができます。

編集:
わかりました。テールを使用している場合は、かなり大まかな「ソリューション」です。おそらくテール以外のものを使用したより良い解決策がありますが、私はそれをあなたに与えなければなりません、テールは壊れたパイプからかなりうまくあなたを導きます。 SIGPIPEを処理できる「T型」のほうがおそらくうまく機能します。 Javaプロセスは、ある種の「im alive」メッセージを使用してファイルシステムのドロップをアクティブに実行しているため、待機するのがさらに簡単になります。

function startServer() {
  touch logfile

  # 30 second timeout.
  sleep 30 &
  timerPid=$!

  tail -n0 -F --pid=$timerPid logfile | while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      # stop the timer..
      kill $timerPid
    fi
  done &

  startJavaprocess > logfile &

  # wait for the timer to expire (or be killed)
  wait %sleep
}
25
falstro

ここで見つけた答えに基づいて、これが私が思いついたものです。

必要なログ出力を確認したら、テールを直接処理して強制終了します。 「pkill -P $$ tail」を使用すると、適切なプロセスが強制終了されます。

wait_until_started() {
    echo Waiting until server is started
    regex='Started'
    tail logfile -n0 -F | while read line; do
            if [[ $line =~ $regex ]]; then
                    pkill -9 -P $$ tail
            fi
    done
    echo Server is started
}
7
David Resnick

tail man page によると、プロセスが終了した後、tailを終了させることができます

BASHでは、$を使用して、最後に開始したバックグラウンドプロセスのPIDを取得できます。 SO bashを使用している場合:

tail -f --pid=$! logfile
6
Robert Christie

同様の状況で、「開始された」メッセージのログを適切な時間内にテールする必要があり、その時間内にログが見つからない場合は、終了する必要があります。これが私がやったことです。

wait_Tomcat_start(){
WAIT=60
echo "Waiting for Tomcat to initialize for $WAIT seconds"

# Tail log file, do a while read loop with a timeout that checks for desired log status,
# if found kill the find and break the loop. If not found within timeout: the read -t will
# kill the while read loop and bounce to the OR statement that will in turn kill the tail 
# and echo some message to the console.
tail -n0 -f $SERVERLOG | while read -t $WAIT LINE || (pkill -f "tail -n0 -f" && echo "Tomcat did not start in a timely fashion! Please check status of Tomcat!!!")
do
        echo "$LINE"
        [[ "${LINE}" == *"Server startup in"* ]] && pkill -f "tail -n0 -f" && break
done
}

これが非常にエレガントであるか、それを行うための最良の方法であるかはわかりませんが、私にとっては十分に機能します。私はどんな意見でも幸せです:)

2
user985216

バックグラウンドプロセスのPIDをキャプチャする

pid=$!

Tailの-pid = PIDオプションを使用して、pid $ PIDを持つプロセスが終了した後に終了するようにします。

2

tail -f logfileをバックグラウンドにしてtailpidwhile readループサブシェルに送信し、trapをEXITに実装してtailコマンドを強制終了することができます。

( (sleep 1; exec tail -f logfile) & echo $! ; wait) | (
  trap 'trap - EXIT; kill "$tailpid"; exit' EXIT
  tailpid="$(head -1)"
  while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      exit 0
    fi
  done
)
1
nurro

私も同じ問題を抱えていて、シンプルで良い解決策を見つけることができませんでした。私はPythonが苦手ですが、どうにかしてこれを解決することができました。

wait_log.py

#!/usr/bin/env python

from optparse import OptionParser
import os
import subprocess
import time

def follow(file):
    def file_size(file):
        return os.fstat(file.fileno())[6]
    def is_newLine(line):
        return line != None and line.find("\n") != -1;

    file.seek(0, os.SEEK_END)

    while True:
        if file.tell() > file_size(file):
            file.seek(0, os.SEEK_END)

        line_start = file.tell()
        line = file.readline()

        if is_newLine(line):
            yield line
        else:
            time.sleep(0.5)
            file.seek(line_start)

def wait(file_path, message):
    with open(file_path) as file:
        for line in follow(file):
            if line.find(message) != -1:
                break

def main():
    parser = OptionParser(description="Wait for a specific message in log file.", usage="%prog [options] message")
    parser.add_option("-f", "--file", help="log file")

    (options, args) = parser.parse_args()

    if len(args) != 1:
        parser.error("message not provided")

    if options.file == None:
        parser.error("file not provided")

    wait(options.file, args[0])

if __name__ == "__main__":
    main()
1
user1008204

プロセスを終了する代わりに、代わりにtail -fプロセスのプロセスIDを見つけて強制終了できます(ログファイルが完了していることが確実であれば、kill -9を使用しても安全です)。

そうすれば、while read lineは自然に終了し、終了する必要はありません。

または、実際にtailを使用して画面に出力していないので、より古い学校を試すこともできます。

grep -q 'Started' logfile
while [[ $? -ne 0 ]] ; do
    sleep 1
    grep -q 'Started' logfile
done
1
paxdiablo

テールプロセスが殺されなかったときに同様の問題がありました

  1. Jschを実行する
  2. tailはjschへの出力を生成しなかったため、その出力ストリームへの出力を生成しませんでした。

--pid=$!を使用して終了し、無限のwhileループを開始して、背後にある何かをエコーし​​てから、背後にあるプロセスが強制終了されたときに強制終了され、テールが強制終了されました。

( while true; do echo 'running';  sleep 5; done ) & ( tail -f --pid=$! log-file )
1
CondorEye

テールに-fコマンドラインオプションの代わりに無限ループを使用するのはどうですか?

function startServer() {
  startJavaprocess > logfile &

  while [ 1 ]
  do
   if tail logfile | grep -q 'Started'; then
    echo 'Server started'
    exit 0
   fi
  done
}
1
Flimm

Grepにパイプ接続されたtail -n0 -fを使用することは確かに素晴らしい解決策であり、パイプ内の最初のプロセスが実際にデッドgrepプロセスに出力しようとすると停止します。

しかし、最後の現在の出力の近くに表示されるテキストを探している場合、grepはすでに(1ブロックで)テールからの入力全体を読み取っているので、これ以上テキスト出力はありません。 grepが終了する前にパイプを送信する必要があるため、パイプを送信する必要があるログ(または、おそらくパイプバッファーに既にあった)-少なくともこれは私の理解です。

Grepで「-m1」オプションを使用すると、期待どおりに動作し、一致した行の直後に入力を残すように見えますが、違いが生じたり、同様の機能を検索したりするのに役立ちませんでした。パイプバッファーはまだテールからのすべてのテキスト出力を保持しているのではないかと思います。あなたはこのpost-grep-matchテキストを次に出力するために残したかったのは、試みたときに尻尾を殺すため(まだ危険です-なんらかの理由で最後の行になるとどうなりますか?)、制御を呼び出し元のスクリプトに戻します。 。

Grepが終了したら、ログファイルの最後に何かを出力するのが1つの方法であることがわかりました。すなわち。

tail -f logfile | (grep -q; echo >>ログファイル)

私の理論では(私の推測が正しい場合)、パイプをこれよりもバッファーなしで機能させることができます、またはおそらくhuponexit設定コマンドを適切なパイプコンポーネントに追加する-つまり、(おそらく中括弧で)助けて;しかし、ログファイルに空白行を追加することは気にしませんでした。問題なく機能し、テストスクリプトは小さいだけです(そのため、他の処理用のフォーマットに固執する必要のある長期のログファイルはありません)。

shopt -s huponexitは便利ですが、そのサブシェル性のためです。

PS私の最初の投稿はここにあります。何かを繰り返すのではなく、既存の回答へのコメントとしてそれをやりたいと思いますが、今はできないと思います。

0
Breezer

これは機能し、サブシェルが死ぬとテールは死ぬはずです


function startServer() {
  touch logfile
  startJavaprocess > logfile &

  while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      exit 0
    fi
  done < <(tail -f logfile)
}

Try this:


function startServer() {
  while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      return 0
    fi
  done < <(startJavaprocess | tee logfile)
}
0
rcarson

元の質問について、なぜexitコマンドが終了しなかったのか、同じ問題が発生し、最終的に原因を見つけました。

Bashのデバッグモードを使用すると、exitコマンドが呼び出されたことがわかりますが、「開始」の直後にログファイルに1行フラッシュするまで、プロセスはハングしています。奇妙なことに、「開始」が出たとき、出口が呼び出されていても、プロセスはまだ何かに引っ掛かっています。 tail -f、もう1行出てくるまで、フックは本当に解放されます。

したがって、開始後にさらに1行印刷すると、モニターはすぐに終了します。

0
Kevin
tail -n0 --pid=$(($BASHPID+1)) -F logfile | sed -n '/Started/{s/.*/Server Started/p; q}'

パイピングの場合、PIDはシーケンシャルなので、テールのpidは$ BASHPIDになり、sedのpidは$ BASHPID + 1になります。 --pidスイッチを指定すると、sedコマンドが終了すると、tailが終了します(正しく!)。このsedコマンドは/ Started /を探し、行全体(。*)を「Server Started」で置き換えて終了します。

0
Christian Herr

前のコマンドをNohupで実行します。

私の場合、Java -jarをNohupで実行します。

Nohup Java -jar trade.jar xx.jar &

ログ出力はありませんが、新しい「Nohup.out」が作成されます。元のログファイルtrade.logも同様に機能します。

その後、tail -f trade.log、シェルはログ情報を表示し、Ctrl-cはそれを中断でき、シェルに戻ります。

0
hatanooh

この問題に対する私の好ましい解決策は、「tail」コマンドとそのコンシューマーをサブシェルに入れ、フィルターロジックに親とその子(tailプロセスを含む)を強制終了させることです。プロセスツリーを見ると、次のようになります。

startServer (pid=101)
   startServer (pid=102) << This is the subshell created by using parens "(...)"
      tail -f logfile (pid=103) << Here's the tail process
      startServer (pid=104)     << Here's the logic that detects the end-marker

このアプローチでは、エンドマーカー検出ロジック(pid 104)は、その親PID(102)とそのすべての子を探し、それ自体を含むバッチ全体を強制終了します。その後、祖父母(上記のpid 101)は自由に続行できます。

function startServer() {
  touch logfile
  startJavaprocess > logfile &

  tail -f logfile | while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      mypid=$BASHPID
      pipeParent=$(awk '/^PPid/ {print $2}' /proc/$mypid/status)
      kill -TERM $pipeParent $(pgrep -P $pipeParent)  # Kill the subshell and kids
    fi
  done
}

# To invoke startServer(), add a set of parens -- that puts it in a subshell:
(startServer())
0
Stabledog

答えを組み合わせて、この簡単な解決策を思いつきました。この例では、Tomcatのstartup.shスクリプトを呼び出してから、catalina.out「サーバーの起動」がログに記録されるまでログを記録し、その後、テーリングを停止します。

#!/bin/bash

function logUntilStarted() {
    tail -n0 -F /home/Tomcat/logs/catalina.out | while read line; do
        if echo $line && echo $line | grep -q 'Server startup' ; then
            pkill -9 -P $$ tail > /dev/null 2>&1
        fi
    done
}

/home/Tomcat/bin/startup.sh
logUntilStarted
0
Matt

末尾を使用しないでください。readを使用して、同じ「ファイル内の最新のものを監視」できます。

ここでは、ログファイルの代わりに [〜#〜] fifo [〜#〜] を使用しています。

function startServer() {
  mkfifo logfile
  startJavaprocess > logfile &

  a=""; while [ "$a" != "Started" ]; do read <logfile a; done

  echo "Server Started"
}

FIFOぶら下がったままになることに注意してください。

0
Alex Brown