PHP的命令列擴充套件Readline相關函式學習

flynike發表於2021-09-09

PHP 作為一個 Web 開發語言,相對來說,命令列程式並不是它的主戰場。所以很多年輕的 PHP 開發者可能連命令列指令碼都沒有寫過,更別提互動式的命令操作了。而今天,我們帶來的這個擴充套件就是針對 PHP 的互動式命令列操作的。

readline 擴充套件函式實現了訪問 GNU Readline 庫的介面。這些函式提供了可編輯的命令列。一個例子是在 Bash 中允許你使用箭頭按鍵來插入字元或者翻看歷史命令。因為這個庫的互動特性,這個功能在你寫的 Web 程式中沒多大用處,但是當你寫的指令碼被用在命令列中時非常有用。

Readline 擴充套件的安裝

Readline 擴充套件已經加入了 PHP 的官方安裝包中,如果是新的 PHP 環境,那麼在編譯的時候加上 --with-readline 即可。另外,我們還需要安裝作業系統的 Readline 庫。當然,如果已經是正常執行的 PHP ,也可以重新編譯一下。

# yum install -y readline-devel
# ./congiure xxxx --with-readline

預設情況下,如果沒有在編譯時增加 --whit-readline ,Readline 的一些函式也是可以使用的,不過它們呼叫的是系統的 libedit 庫。有一些函式,比如 readline_list_history() 這種函式是無法使用的。要想完整的使用 Readline 擴充套件的能力,那麼還是需要安裝作業系統的 libreadline 庫(上面 yum 安裝的那個 readline-devel )並在 PHP 中進行相應引數的編譯安裝。

基本函式操作

Readline 擴充套件提供的函式不多,也非常的簡單易用。

讀取一行

$line = readline("請輸入命令:"); // 讀取命令列互動資訊
echo $line, PHP_EOL; // aaa

執行 PHP 程式碼後,我們就進入了命令提示符等待狀態,並且會提示“請輸入命令:”,當我們輸入了 aaa 並回車之後,輸入的內容就儲存到了 $line 變數中。

命令歷史列表相關操作

Readline 很強大的一個功能就是它自帶一套命令歷史記錄的功能。不過這個需要我們自己手動地將命令加入到命令歷史中。

$line = readline("請輸入命令:"); // 讀取命令列互動資訊
if (!empty($line)) {
    readline_add_history($line); // 需要手動加入到命令歷史記錄中
}
echo $line, PHP_EOL; // aaa

$line = readline("請輸入命令:");
if (!empty($line)) {
    readline_add_history($line);
}

// 命令歷史記錄列表
print_r(readline_list_history());
// Array
// (
//     [0] => aaa
//     [1] => bbb
// )

使用 readline_add_history() 函式,就可以將一條命令加入到命令歷史記錄中,然後使用 readline_list_history() 就能夠列印出我們之前在互動式環境中傳送過的命令記錄。當然,如果只是這樣簡單的儲存再列印那就沒意思了,它還能將這些歷史資訊儲存到外部檔案進行儲存。

// 將命令歷史記錄寫入到一個檔案中
readline_write_history('./readline_history');
// ./readline_history中
// _HiStOrY_V2_
// aaa
// bbb

// 清理命令歷史記錄
readline_clear_history();
print_r(readline_list_history());
// Array
// (
// )

// 從檔案中讀取命令歷史記錄
readline_read_history('./readline_history');
print_r(readline_list_history());
// Array
// (
//     [0] => bbb
//     [1] => bbb
// )

我們使用 readline_write_history() 函式將當前的命令歷史記錄儲存到一個檔案中,然後使用 readline_clear_history() 清理掉目前命令歷史記錄列表中的內容,這個時候列印 readline_list_history() 的話裡面已經沒有任何東西了。接著,我們再使用 readline_read_history() 將命令的歷史記錄從檔案中載入回來進行還原。這一套功能是不是就非常有意思了,我們可以記錄客戶的所有命令操作,不管是安全審查還是事件回放,都非常有用。

檢視 Readline 狀態

// 當前命令列內部的變數資訊
print_r(readline_info());
// Array
// (
//     [line_buffer] => bbb
//     [point] => 3
//     [end] => 3
//     [mark] => 0
//     [done] => 1
//     [pending_input] => 0
//     [prompt] => 請輸入命令:
//     [terminal_name] => xterm-256color
//     [completion_append_character] =>
//     [completion_suppress_append] =>
//     [library_version] => 7.0
//     [readline_name] => other
//     [attempted_completion_over] => 0
// )

readline_info() 函式就比較簡單了,我們可以看到最後一條互動式命令的資訊,裡面包括了命令輸入的內容 line_buffer ,內容長度 point ,提示資訊 prompt 等內容。

命令提示效果

在 Linux 等作業系統上,我們想不起一個命令的全拼沒關係,只需要記住它的前幾個字元然後按兩個 Tab 鍵就可以得到相關的命令提示了。Readline 擴充套件庫當然也為我們準備了這樣的功能。

// 類似於命令列中按 Tab 鍵的提示效果
readline_completion_function(function ($input, $index) {
    $commands = ['next', 'exit', 'quit'];
    $matches = [];
    if ($input) {
        // 如果關鍵字包含在命令中,提示命令資訊
        foreach ($commands as $c) {
            if (strpos($c, $input) !== false) {
                $matches[] = $c;
            }
        }
    }else{
        $matches = $commands;
    }
    return $matches;
});

// 使用 Tab 鍵測試一下吧
$line = trim(readline("請輸入命令:"));
if (!empty($line)) {
    readline_add_history($line);
}
echo $line, PHP_EOL; // 當前輸入的命令資訊
// 如果命令是 exit 或者 quit ,就退出程式執行
if($line == 'exit' || $line == 'quit'){
    exit;
}

readline_completion_function() 函式會接收一個回撥函式,當在互動式命令列模式下,也就是 readline 函式呼叫時,按下 Tab 鍵的時候,就會進入到這個函式的回撥函式中。KaTeX parse error: Expected 'EOF', got '是' at position 7: input 是̲當前已經輸入內容的值,index 是第幾個字元。我們在這個回撥函式中定義了幾個預設的命令,當你鍵入一個 n 時直接按 Tab 鍵,程式就是提示出完整的 next 命令出來。當然,多個相同的字母開頭的都是可以透過這個 $matches 陣列返回呈現的。

此外,在這段程式碼中,如果我們輸入了 exit 或者 quit 。將退出程式的執行。

字元回撥操作相關示例

最後幾個函式我們將透過一個複雜的小測試來學習。

// 輸出的內容進入這個回撥函式中
function rl_callback($ret)
{
    global $c, $prompting;

    echo "您輸入的內容是: $retn";
    $c++;

    readline_add_history($ret);

    // 限制了就呼叫10次,也可以透過命令列輸入的內容來判斷,比如上面的 exit 那種進行退出
    if ($c > 10) {
        $prompting = false;
        // 移除上一個安裝的回撥函式控制程式碼並且恢復終端設定
        readline_callback_handler_remove();
    } else {
        // 繼續進行遞迴回撥
        readline_callback_handler_install("[$c] 輸入點什麼內容: ", 'rl_callback');

    }
}

$c = 1;
$prompting = true;

// 初始化一個 readline 回撥介面,然後終端輸出提示資訊並立即返回,需要等待 readline_callback_read_char() 函式呼叫後才會進入到回撥函式中
readline_callback_handler_install("[$c] 輸入點什麼內容: ", 'rl_callback');

// 當 $prompting 為 ture 時,一直等待輸入資訊
while ($prompting) {
    $w = null;
    $e = null;
    $r = array(STDIN);
    $n = stream_select($r, $w, $e, null);
    if ($n && in_array(STDIN, $r)) {
        // 當一個行被接收時讀取一個字元並且通知 readline 呼叫回撥函式
        readline_callback_read_char();
    }
}

echo "結束,完成所有輸入!n";
// [1] 輸入點什麼內容: A
// 您輸入的內容是: A
// [2] 輸入點什麼內容: B
// 您輸入的內容是: B
// [3] 輸入點什麼內容: C
// 您輸入的內容是: C
// [4] 輸入點什麼內容: D
// 您輸入的內容是: D
// [5] 輸入點什麼內容: E
// 您輸入的內容是: E
// [6] 輸入點什麼內容: F
// 您輸入的內容是: F
// [7] 輸入點什麼內容: G
// 您輸入的內容是: G
// [8] 輸入點什麼內容: H
// 您輸入的內容是: H
// [9] 輸入點什麼內容: I
// 您輸入的內容是: I
// [10] 輸入點什麼內容: J
// 您輸入的內容是: J
// 結束,完成所有輸入!

print_r(readline_list_history());
// Array
// (
//     [0] => A
//     [1] => B
//     [2] => C
//     [3] => D
//     [4] => E
//     [5] => F
//     [6] => G
//     [7] => H
//     [8] => I
//     [9] => J
// )

首先,我們先不管上面的這個自定義的函式,直接向下看到 readline_callback_read_char() 。它的作用是當一個行被接收時讀取一個字元並且通知 readline 呼叫回撥函式。也就是當一行輸入完成後,鍵入了回車之後,這個函式將通知 Readline 元件去呼叫 readline_callback_handler_install() 註冊的回撥函式。

readline_callback_handler_install() 函式的功能是初始化一個 readline 回撥介面,然後終端輸出提示資訊並立即返回,如果在回撥函式中不進行什麼操作的話,這個函式就只是輸出一個提示就結束了。在我們例子中的這個回撥函式 rl_callback() 中,我們根據當前接收命令的次數,判斷如果接收的命令在十次內,則繼續接收命令直到十次命令為止就呼叫 readline_callback_handler_remove() 移除上一個 readline_callback_handler_install() 安裝的回撥並恢復終端的預設設定。

最後執行的結果就是註釋中的內容,大家也可以自己複製下程式碼後執行除錯,只有自己進行過的除錯才能理解的更加深入。

總結

Readline 很強大,而且也是 PHP 預設安裝包中自帶的擴充套件。一般被加入預設的擴充套件都是經過時間檢驗而且非常有用的擴充套件,大家可以根據這些內容再進行更加深入的學習並運用到實戰中。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2618/viewspace-2797712/,如需轉載,請註明出處,否則將追究法律責任。

相關文章