プロセスの無限ループ入力リダイレクトの終了

プロセスの無限ループ入力リダイレクトの終了

編集: 実行しようとしていたプログラムが、入力をすべてスキャンした後に無限ループになっていることに気付きました。その後、無限に出力するため、EOF は読み込まれません。最後の入力を読み込んだ後、無限ループに入ります。

たとえば、次のようなプログラムがあるとします。

printf("%c\n", getch());
while(1)
{
   printf("%c\n", getch());
}

すべての入力を読み込んだ後にプログラムを強制終了する方法はありますか? 入力ファイルのリダイレクトが完了したらプログラムを強制終了できるようにしたいです。(プログラムが入力ファイルから最後の文字を取得したとき) 注: 実行中のプログラムを変更することはできませんが、このプログラムを実行するスクリプトを変更することはできます。

現在、プログラムを実行するためにスクリプトにこれを含めています。基本的に、これは入力と出力を結合/マージするのに役立ちます。

編集: 誰かが私の以前の問題を解決するのを手伝ってくれました。この strace に || break を追加するはずでしたが、上部の編集で新しい問題が発生しました。

mkfifo strace.fifo
{
  while read -d, trace; do
    if [[ $trace = *"read(0" ]]; then
       IFS= read -rn1 answer <&3 || break
       echo "$answer" >> in
       answer=${answer:-$'\n'}
       printf %s "$answer" >&2
       printf %s "$answer"
    fi
  done < strace.fifo 3< input | strace -o strace.fifo -e read stdbuf -o0 ./a.out &
} >> $fname.out 2>&1

ということで、現在私はスリープを使用してプログラムを強制終了することでプログラムを終了しようとする非常にハッキーな方法を持っています。また、これまでに使用した入力をチェックし、それを入力ファイルと比較しています。これは私が行っていることのほんの一例です。私の現在のスクリプトには、以下のコードに似た複数のタイマーと if 比較があります。ただし、この方法は良い方法ではありません。なぜなら、指定した秒数後にプログラムが入力ファイルの読み取りを完了しないと、プログラムは強制終了されないからです。実際のスクリプトでは、これらの if ステートメントを複数使用し、80% の時間で機能しますが、タイマーとプログラムの実行方法の変動が原因で、正しく動作しないこともあります。

sleep .05
awk 'BEGIN{ORS="";} NR==1{print; next;} /^[^ ]/ { print; next; } {print "\n";}' in > temp
diff -w -B temp input > /dev/null
# Check if input done yet
if [ $? -eq 0 ] ; then
   # Check if program still running and input done
   if [ "$(pidof a.out)" ] ; then
      killall -15 a.out > /dev/null 2>&1
   fi
fi

killそれで、入力が完了した後にプロセスを実行する方法はあるのだろうかと思っていますstrace。また、入力と出力を結合したままにしておきたいです。

答え1

もっと簡単な解決策があると思いますが、それがどの程度移植可能かはわかりません。次の点を考慮してください。

$ cat trunc.c
#include <stdio.h>

int main() {
    int c;
    while(((c = getchar()) != EOF) && ((c & 0xff) != 0xff))
        putchar(c);
    return 0; 
}

これは、2 の補数マシンでは という事実を利用します(-1 & 0xff) == 0xff。これにより、EOF を文字として印刷するプログラムが捕捉されます。私はこれをテストしました:

your_buggy_source_gen < any_ascii_file | trunc

cat any_ascii_fileこれは、値 0xff のオクテットが含まれていない場合と同等です。

答え2

「今、すべての入力が読み取られる前に、プログラムが無限ループに入っているかどうかを確認する方法はあるのだろうかと思っています。たとえば、プログラムが無限ループに入った場合、プログラムを停止する唯一の方法はそれです。」

私たちは答えを知っていますその疑問は80年近くも続いてきた答えはノーです。方法はありません。

実際の質問とは全く違う質問をしていただきありがとうございます。

「生徒のアウトプットを採点しようとしています。将来使用できるようにスクリプトを作成したいだけです。現在はスリープ タイマーを使用してうまく動作していますが、タイマーなしでスクリプトをより堅牢にしたいと考えています。」

簡単な解決策:

# how many seconds of CPU (not wall clock)
# should the student's program get?
# 10 seconds is generous for an intro class
# 60 seconds of CPU would be obscene
$ ulimit -t 10
$ run_student_program

# but since I don't have a student program

$ time dd if=/dev/zero of=/dev/null
Killed

real    0m9.998s
user    0m3.080s
sys     0m6.912s

答え3

正確にはシェルプログラミングのヒントではありませんが、実際には、ソースコードを変更できない場合でも、壊れたプログラムの動作を変更する必要があります。

これを実現する 1 つの方法は、実際の "read()" システム コールを呼び出して、ファイル終了条件に遭遇したときに終了するという方法で "read()" システム コールをインターセプトする LD_PRELOAD モジュールです。(理想的には "getchar()" をインターセプトしますが、これは通常関数呼び出しではなくマクロであるため、インターセプトできません。)

別の方法としては、デバッガーでコードを実行し、無限ループが発生する場所を見つけて、実行可能ファイルを変更するか、または誤った関数を置き換える LD_PRELOAD モジュールを適用してバイナリにパッチを適用するという方法があります。

あるいは、前の回答で示唆されているように、誤ったプログラムが入力の終わりに達したことを示す何かを出力したときに入力を閉じるものに出力をパイプすることもできます。(これはそれに依存していることに注意してくださいないSIGPIPE をトラップしますが、これはこの場合妥当な仮定です。

これのより洗練されたバージョンは、入力ファイル記述子を、そこから読み取らずに終わりに達したかどうかを監視する別のプログラムと共有することです。ただし、これは、特にパイプやソケット、デバイスを扱う場合には、非常に難しい作業です。

誤ったプログラムが stdin から読み取っていると仮定すると、次のようにラップします。

  #!/bin/sh
  不正なプログラム「$@」を実行 &
  pid=$!
  perl -MFcntl=:mode -e '
    @S = stat STDIN または die "無効な入力; $!\n";
    S_IFREG($S[2]) または die "入力はプレーンファイルではありません\n";
    while (sysseek(STDIN,0,1) != $S[7]) { スリープ 1 }
  '
  $pid を強制終了する
  待って

答え4

壊れたプログラムを修正する必要がありますが、それができない場合は、シェルでファイルを cat してプログラムにパイプし、終了する前に 1 秒か 2 秒スリープしてパイプを閉じ、SIGPIPE でプログラムを強制終了することで回避できます。

(cat file ; sleep 2) | stupid_program

関連情報