29

私は以下を持っています。

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

tail -fコードにexit0が含まれていても、プロセスを終了できません。

これでスクリプトを終了できません。Bashでこれを行う他の方法はありますか?

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

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
}
4

18 に答える 18

27

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

  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を処理できる「ティー」の方がおそらくうまくいくでしょう。ある種の「imalive」メッセージを使用してファイルシステムのドロップをアクティブに実行しているJavaプロセスは、おそらく待つのがさらに簡単です。

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
}
于 2010-01-11T11:34:40.150 に答える
8

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

テールを直接処理し、必要なログ出力を確認したらそれを強制終了します。'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
}
于 2011-01-30T07:42:37.900 に答える
7

テールマンページによると、プロセスが終了した後にテールを終了させることができます

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

tail -f --pid=$! logfile
于 2010-01-11T11:35:07.100 に答える
3

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

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
}

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

于 2012-08-20T08:07:16.290 に答える
2

バックグラウンドプロセスのpidをキャプチャします

pid=$!

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

于 2010-01-11T11:35:49.443 に答える
1

プロセスを終了するのではなく、代わりにプロセスのプロセスIDを見つけて強制終了することができますtail -fkill -9ログファイルが終了していることが確実な場合は、ここでも安全です)。

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

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

grep -q 'Started' logfile
while [[ $? -ne 0 ]] ; do
    sleep 1
    grep -q 'Started' logfile
done
于 2010-01-11T11:35:28.190 に答える
1

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

function startServer() {
  startJavaprocess > logfile &

  while [ 1 ]
  do
   if tail logfile | grep -q 'Started'; then
    echo 'Server started'
    exit 0
   fi
  done
}
于 2010-01-11T11:48:58.023 に答える
1

私は同じ問題を抱えていましたが、簡単で良い解決策を見つけることができませんでした。私は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()
于 2011-10-22T05:47:36.810 に答える
1

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

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

を使用し--pid=$!てそれを強制終了し、無限のwhileループを開始して、基になるプロセスが強制終了されたときに終了してテールを強制終了するテールの前にバックグラウンドで何かをエコーし​​ます。

( while true; do echo 'running';  sleep 5; done ) & ( tail -f --pid=$! log-file )
于 2012-07-31T11:13:16.680 に答える
1

この問題に対する私の好ましい解決策は、「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())
于 2013-12-03T17:30:41.850 に答える
1

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

( (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
)
于 2016-03-21T09:41:21.930 に答える
0

テールを使用しないでください。同じ「ファイル内の最新のものを監視する」を使用して取得できますread

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

function startServer() {
  mkfifo logfile
  startJavaprocess > logfile &

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

  echo "Server Started"
}

これにより、FIFOがぶら下がってしまうことに注意してください。

于 2010-01-11T11:44:49.280 に答える
0

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


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)
}
于 2010-01-12T03:26:04.743 に答える
0

grepにパイプされたtail-n0-fを使用することは確かに優れたソリューションであり、実際、パイプ内の最初のプロセスは、デッドgrepプロセスに出力しようとすると停止します。

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

grepで「-m1」オプションを使用すると、希望どおりの結果が得られ、一致した行の直後に入力が残るように見えますが、違いが見られなかったり、同様の機能を検索するのに役立ったりしませんでした。パイプバッファがまだtailからのすべてのテキスト出力を保持しているのではないか、またはtailが出力するものを何も残していない他の理由があるのではないかと思います。このgrep一致後のテキストを次に出力したいのは、試行したときにテールが殺されるため(まだ危険です。何らかの理由で最後の行があった場合はどうなりますか?)、呼び出し元のスクリプトに制御を戻します。 。

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

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

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

shopt -s huponexitは便利ですが、そのサブシェル性には役立ちます。

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

于 2011-06-27T20:31:31.517 に答える
0

元の質問であるexitコマンドが終了しなかった理由については、同じ問題が発生し、最終的に原因が見つかりました。

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

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

于 2012-07-12T10:45:40.433 に答える
0

答えを組み合わせて、この簡単な解決策を思いつきました。この例では、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
于 2016-03-22T19:58:28.943 に答える
0

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

私の場合、nohupを指定してjava-jarを実行します。

nohup java -jar trade.jar xx.jar &

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

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

于 2018-08-28T03:53:48.337 に答える
-1
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/を検索し、行全体(。*)を「ServerStarted」に置き換えて終了します。

于 2017-04-21T16:04:11.837 に答える