こんにちは!今回はPHPのPOSIX関数の中から、posix_isatty関数について詳しく解説していきます。CLIスクリプトを開発する際に非常に便利な「端末(TTY)判定」の方法を、実践的なサンプルコードと共にお伝えします。
posix_isatty関数とは?
posix_isattyは、指定したファイルディスクリプタが**端末(TTY: TeleTYpewriter)**に接続されているかどうかを判定する関数です。CLIスクリプトが対話的に実行されているか、パイプやリダイレクトで実行されているかを判別できます。
基本構文
posix_isatty(resource|int $file_descriptor): bool
- 引数: ファイルディスクリプタ(リソースまたは整数)
- 戻り値: TTYの場合
true、それ以外はfalse
標準ファイルディスクリプタ
STDIN // 0 - 標準入力
STDOUT // 1 - 標準出力
STDERR // 2 - 標準エラー出力
TTY(端末)とは?
TTYは、ユーザーが対話的に操作している端末デバイスのことです。
TTYに接続されている場合
# ターミナルで直接実行
$ php script.php
→ STDIN, STDOUT, STDERR は全てTTYに接続
TTYに接続されていない場合
# パイプで実行
$ echo "data" | php script.php
→ STDIN はパイプ(TTYではない)
# ファイルにリダイレクト
$ php script.php > output.txt
→ STDOUT はファイル(TTYではない)
# バックグラウンド実行
$ php script.php &
→ 端末から切り離されている
実践的な使い方
例1: 基本的なTTY判定
<?php
// 標準入力がTTYかチェック
if (posix_isatty(STDIN)) {
echo "対話モードで実行中です\n";
} else {
echo "パイプまたはリダイレクトで実行中です\n";
}
// 標準出力がTTYかチェック
if (posix_isatty(STDOUT)) {
echo "出力は端末に表示されます\n";
} else {
echo "出力はリダイレクトされています\n";
}
?>
例2: カラー出力の自動切り替え
<?php
class ColorOutput {
private $color_enabled;
public function __construct() {
// 標準出力がTTYの場合のみカラー出力を有効化
$this->color_enabled = posix_isatty(STDOUT);
}
public function success($message) {
if ($this->color_enabled) {
echo "\033[32m✓ {$message}\033[0m\n"; // 緑色
} else {
echo "✓ {$message}\n";
}
}
public function error($message) {
if ($this->color_enabled) {
echo "\033[31m✗ {$message}\033[0m\n"; // 赤色
} else {
echo "✗ {$message}\n";
}
}
public function warning($message) {
if ($this->color_enabled) {
echo "\033[33m⚠ {$message}\033[0m\n"; // 黄色
} else {
echo "⚠ {$message}\n";
}
}
public function info($message) {
if ($this->color_enabled) {
echo "\033[36mℹ {$message}\033[0m\n"; // シアン
} else {
echo "ℹ {$message}\n";
}
}
}
// 使用例
$output = new ColorOutput();
$output->success("処理が完了しました");
$output->error("エラーが発生しました");
$output->warning("警告: 設定ファイルが見つかりません");
$output->info("情報: データベースに接続中...");
?>
例3: 対話的な入力処理
<?php
function readUserInput($prompt) {
// STDINがTTYの場合のみプロンプトを表示
if (posix_isatty(STDIN)) {
echo $prompt;
}
$input = trim(fgets(STDIN));
return $input;
}
// 使用例
$name = readUserInput("名前を入力してください: ");
$age = readUserInput("年齢を入力してください: ");
echo "\n入力された情報:\n";
echo "名前: {$name}\n";
echo "年齢: {$age}\n";
?>
例4: プログレスバーの表示制御
<?php
class ProgressBar {
private $total;
private $current = 0;
private $show_progress;
public function __construct($total) {
$this->total = $total;
// TTYの場合のみプログレスバーを表示
$this->show_progress = posix_isatty(STDOUT);
}
public function advance($step = 1) {
$this->current += $step;
if ($this->show_progress) {
$this->render();
}
}
private function render() {
$percent = ($this->current / $this->total) * 100;
$bar_length = 50;
$filled = round($bar_length * $this->current / $this->total);
$empty = $bar_length - $filled;
$bar = str_repeat('█', $filled) . str_repeat('░', $empty);
// カーソルを行の先頭に戻して上書き
echo "\r[{$bar}] {$percent}% ({$this->current}/{$this->total})";
if ($this->current >= $this->total) {
echo "\n";
}
}
public function finish() {
if ($this->show_progress && $this->current < $this->total) {
$this->current = $this->total;
$this->render();
}
}
}
// 使用例
echo "処理を開始します...\n";
$progress = new ProgressBar(100);
for ($i = 0; $i < 100; $i++) {
// 何か処理を実行
usleep(50000); // 0.05秒待機
$progress->advance();
}
$progress->finish();
echo "処理が完了しました!\n";
?>
例5: 実行環境の自動検出
<?php
class ExecutionEnvironment {
private $stdin_tty;
private $stdout_tty;
private $stderr_tty;
public function __construct() {
$this->stdin_tty = posix_isatty(STDIN);
$this->stdout_tty = posix_isatty(STDOUT);
$this->stderr_tty = posix_isatty(STDERR);
}
public function isInteractive() {
// 入出力の両方がTTYなら対話モード
return $this->stdin_tty && $this->stdout_tty;
}
public function isPiped() {
// 入力または出力がパイプされている
return !$this->stdin_tty || !$this->stdout_tty;
}
public function getMode() {
if ($this->stdin_tty && $this->stdout_tty && $this->stderr_tty) {
return 'interactive';
} elseif (!$this->stdin_tty && $this->stdout_tty) {
return 'piped_input';
} elseif ($this->stdin_tty && !$this->stdout_tty) {
return 'piped_output';
} else {
return 'fully_piped';
}
}
public function getDetails() {
return [
'mode' => $this->getMode(),
'stdin_is_tty' => $this->stdin_tty,
'stdout_is_tty' => $this->stdout_tty,
'stderr_is_tty' => $this->stderr_tty,
'interactive' => $this->isInteractive(),
];
}
}
// 使用例
$env = new ExecutionEnvironment();
$details = $env->getDetails();
echo "=== 実行環境情報 ===\n";
echo "モード: {$details['mode']}\n";
echo "STDIN (TTY): " . ($details['stdin_is_tty'] ? 'Yes' : 'No') . "\n";
echo "STDOUT (TTY): " . ($details['stdout_is_tty'] ? 'Yes' : 'No') . "\n";
echo "STDERR (TTY): " . ($details['stderr_is_tty'] ? 'Yes' : 'No') . "\n";
echo "対話モード: " . ($details['interactive'] ? 'Yes' : 'No') . "\n";
?>
よくある使用シーン
1. カラー出力の自動制御
<?php
function colorize($text, $color_code) {
// 端末の場合のみカラーコードを追加
if (posix_isatty(STDOUT)) {
return "\033[{$color_code}m{$text}\033[0m";
}
return $text;
}
// カラーコード
// 31: 赤, 32: 緑, 33: 黄, 34: 青, 35: マゼンタ, 36: シアン
echo colorize("エラーメッセージ", "31") . "\n";
echo colorize("成功メッセージ", "32") . "\n";
echo colorize("警告メッセージ", "33") . "\n";
?>
2. 対話的な確認ダイアログ
<?php
function confirm($message, $default = false) {
// 非TTYの場合はデフォルト値を返す
if (!posix_isatty(STDIN)) {
return $default;
}
$default_str = $default ? '[Y/n]' : '[y/N]';
echo "{$message} {$default_str}: ";
$input = strtolower(trim(fgets(STDIN)));
if ($input === '') {
return $default;
}
return in_array($input, ['y', 'yes']);
}
// 使用例
if (confirm("ファイルを削除しますか?", false)) {
echo "ファイルを削除しました\n";
} else {
echo "キャンセルしました\n";
}
?>
3. ログ出力の形式切り替え
<?php
class Logger {
private $is_tty;
public function __construct() {
$this->is_tty = posix_isatty(STDOUT);
}
public function log($level, $message) {
$timestamp = date('Y-m-d H:i:s');
if ($this->is_tty) {
// TTYの場合: カラフルで読みやすい形式
$colors = [
'ERROR' => "\033[31m", // 赤
'WARN' => "\033[33m", // 黄
'INFO' => "\033[36m", // シアン
'DEBUG' => "\033[37m", // 白
];
$color = $colors[$level] ?? "\033[0m";
echo "{$color}[{$level}]\033[0m {$message}\n";
} else {
// 非TTYの場合: 構造化されたログ形式
echo json_encode([
'timestamp' => $timestamp,
'level' => $level,
'message' => $message
]) . "\n";
}
}
}
// 使用例
$logger = new Logger();
$logger->log('INFO', 'アプリケーションを起動しました');
$logger->log('WARN', 'キャッシュファイルが見つかりません');
$logger->log('ERROR', 'データベース接続に失敗しました');
?>
4. デバッグ情報の表示制御
<?php
function debug($var, $label = null) {
// TTYの場合のみデバッグ情報を表示
if (!posix_isatty(STDOUT)) {
return;
}
if ($label) {
echo "\033[35m[DEBUG]\033[0m {$label}:\n";
} else {
echo "\033[35m[DEBUG]\033[0m\n";
}
print_r($var);
echo "\n";
}
// 使用例
$data = ['name' => 'John', 'age' => 30, 'city' => 'Tokyo'];
debug($data, 'ユーザーデータ');
$result = processData($data);
debug($result, '処理結果');
?>
5. スピナーアニメーション
<?php
class Spinner {
private $frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
private $current = 0;
private $enabled;
public function __construct() {
$this->enabled = posix_isatty(STDOUT);
}
public function spin($message = '') {
if (!$this->enabled) {
return;
}
$frame = $this->frames[$this->current];
$this->current = ($this->current + 1) % count($this->frames);
echo "\r{$frame} {$message}";
}
public function clear() {
if ($this->enabled) {
echo "\r" . str_repeat(' ', 80) . "\r";
}
}
}
// 使用例
$spinner = new Spinner();
for ($i = 0; $i < 50; $i++) {
$spinner->spin("処理中... ({$i}/50)");
usleep(100000); // 0.1秒
}
$spinner->clear();
echo "処理が完了しました!\n";
?>
注意点とトラブルシューティング
Windows環境での動作
Windows環境ではposix_isattyが使用できない場合があります。
<?php
function isTTY($fd) {
if (function_exists('posix_isatty')) {
return posix_isatty($fd);
}
// Windows環境のフォールバック
if (DIRECTORY_SEPARATOR === '\\') {
// Windowsではstream_isattyを使用(PHP 7.2+)
if (function_exists('stream_isatty')) {
return stream_isatty($fd);
}
return false;
}
return false;
}
// 使用例
if (isTTY(STDOUT)) {
echo "端末出力\n";
}
?>
環境変数による判定
より堅牢な判定には環境変数も併用します。
<?php
function isReallyInteractive() {
// POSIX判定
if (!posix_isatty(STDIN) || !posix_isatty(STDOUT)) {
return false;
}
// 環境変数チェック
$term = getenv('TERM');
if ($term === false || $term === 'dumb') {
return false;
}
return true;
}
if (isReallyInteractive()) {
echo "完全な対話モード\n";
}
?>
CI/CD環境での考慮
<?php
function isCIEnvironment() {
$ci_vars = ['CI', 'CONTINUOUS_INTEGRATION', 'GITHUB_ACTIONS', 'GITLAB_CI'];
foreach ($ci_vars as $var) {
if (getenv($var)) {
return true;
}
}
return false;
}
function shouldUseColor() {
// CI環境ではカラー出力を無効化(設定による)
if (isCIEnvironment()) {
return getenv('FORCE_COLOR') !== false;
}
// 通常はTTY判定
return posix_isatty(STDOUT);
}
?>
関連する便利な関数
ファイルディスクリプタ関連
<?php
// ファイルのリソースから判定
$file = fopen('output.txt', 'w');
if (posix_isatty($file)) {
echo "ファイルはTTYです\n";
} else {
echo "ファイルはTTYではありません\n";
}
fclose($file);
// 数値のファイルディスクリプタで判定
if (posix_isatty(0)) { // STDIN
echo "標準入力はTTY\n";
}
if (posix_isatty(1)) { // STDOUT
echo "標準出力はTTY\n";
}
if (posix_isatty(2)) { // STDERR
echo "標準エラー出力はTTY\n";
}
?>
PHP 7.2+のstream_isatty
<?php
// PHP 7.2以降では標準関数が使用可能
if (function_exists('stream_isatty')) {
if (stream_isatty(STDOUT)) {
echo "stream_isattyでもTTY判定可能\n";
}
}
?>
まとめ
posix_isatty関数は以下の特徴があります。
✅ 端末(TTY)接続を判定 ✅ カラー出力の自動制御に最適 ✅ 対話的UIとバッチ処理の切り替え ✅ プログレスバーやスピナーの表示制御 ✅ ログ形式の自動調整
CLIツールを開発する際、ユーザー体験を向上させるためにposix_isattyは非常に重要な関数です。端末で実行されているかどうかを判定し、適切な出力形式を選択することで、より使いやすいツールを作成できます。
実用的なTips
- カラー出力は端末のみ: ログファイルに色コードを書き込まない
- プログレスバーは端末のみ: パイプ時は簡潔なログ出力
- 対話的入力は端末のみ: 自動化を妨げない設計
- エラーはSTDERRへ: 出力のリダイレクトに配慮
より良いCLIツールを作りましょう! この記事が役に立ったら、ぜひシェアしてください。PHPのCLIプログラミングについて、他にも知りたいことがあればコメントで教えてくださいね。
