web-dev-qa-db-ja.com

PHP CLI:TTYから入力の1文字を読み取る方法(Enterキーを待たずに)?

PHPのコマンドラインから一度に1文字ずつ読み取りたいのですが、どこかから何らかの入力バッファリングがあり、これを妨げているようです。

このコードを検討してください:

#!/usr/bin/php
<?php
echo "input# ";
while ($c = fread(STDIN, 1)) {
    echo "Read from STDIN: " . $c . "\ninput# ";
}
?>

入力として「foo」と入力して(そしてEnterキーを押して)、私が得ている出力は次のとおりです。

input# foo
Read from STDIN: f
input# Read from STDIN: o
input# Read from STDIN: o
input# Read from STDIN: 

input# 

私の出力期待は次のとおりです。

input# f
input# Read from STDIN: f

input# o
input# Read from STDIN: o

input# o
input# Read from STDIN: o

input# 
input# Read from STDIN: 

input# 

(つまり、文字は入力時に読み取られて処理されます)。

ただし、現在、各文字はEnterキーを押した後にのみ読み取られます。 TTYが入力をバッファリングしているのではないかと疑っています。

最終的には、上矢印、下矢印などのキー押下を読み取れるようにしたいと思います。

25
dansimau

私にとっての解決策は、TTYで-icanonモードを設定することでした(sttyを使用)。例えば。:

stty -icanon

したがって、現在機能するコードは次のとおりです。

#!/usr/bin/php
<?php
system("stty -icanon");
echo "input# ";
while ($c = fread(STDIN, 1)) {
    echo "Read from STDIN: " . $c . "\ninput# ";
}
?>

出力:

input# fRead from STDIN: f
input# oRead from STDIN: o
input# oRead from STDIN: o
input# 
Read from STDIN: 

input# 

ここで与えられた答えへの小道具:
(リモート)ターミナルセッションからキーが押されるのを待って取得する方法はありますか?

詳細については、以下を参照してください。
http://www.faqs.org/docs/Linux-HOWTO/Serial-Programming-HOWTO.html#AEN92

使い終わったら、TTYを復元することを忘れないでください...

tty構成の復元

ターミナルを元の状態にリセットするには、変更を加える前にttyの状態を保存します。完了したら、その状態に復元できます。

例えば:

<?php

// Save existing tty configuration
$term = `stty -g`;

// Make lots of drastic changes to the tty
system("stty raw opost -ocrnl onlcr -onocr -onlret icrnl -inlcr -echo isig intr undef");

// Reset the tty back to the original configuration
system("stty '" . $term . "'");

?>

これは、ttyを保存し、開始前にユーザーが持っていた方法に戻す唯一の方法です。

元の状態を保持する必要がない場合は、次の手順を実行するだけで、デフォルトの「正常な」構成にリセットできることに注意してください。

<?php

// Make lots of drastic changes to the tty
system("stty raw opost -ocrnl onlcr -onocr -onlret icrnl -inlcr -echo isig intr undef");

// Reset the tty back to sane defaults
system("stty sane");

?>
33
dansimau

これは、ttyのものをいじる必要なしに、readlineおよびstream関数で私のために働く方法です。

readline_callback_handler_install('', function() { });
while (true) {
  $r = array(STDIN);
  $w = NULL;
  $e = NULL;
  $n = stream_select($r, $w, $e, null);
  if ($n && in_array(STDIN, $r)) {
    $c = stream_get_contents(STDIN, 1);
    echo "Char read: $c\n";
    break;
  }
}

OSXでPHP 5.5.8)でテスト済み。

22
seb

以下の関数は、単一の文字をキャプチャするために使用できる@sebの回答の簡略版です。 stream_selectを必要とせず、whileループを作成するのではなく、readline_callback_handler_installの固有のブロッキングを使用します。また、ハンドラーを削除して、通常どおりさらに入力できるようにします(readlineなど)。

function readchar($Prompt)
{
    readline_callback_handler_install($Prompt, function() {});
    $char = stream_get_contents(STDIN, 1);
    readline_callback_handler_remove();
    return $char;
}

// example:
if (!in_array(
    readchar('Continue? [Y/n] '), ["\n", 'y', 'Y']
    // enter/return key ("\n") for default 'Y'
)) die("Good Bye\n");
$name = readline("Name: ");
echo "Hello {$name}.\n";
4
Synexis
<?php
`stty -icanon`;
// this will do it
stream_set_blocking(STDIN, 0);
echo "Press 'Q' to quit\n";
while(1){
   if (ord(fgetc(STDIN)) == 113) {
       echo "QUIT detected...";
       break;
   }
   echo "we are waiting for something...";
}
0
joeldg