2025, Sep 24 03:33
चाइल्ड PTY से इंटरैक्टिव प्रोग्राम नियंत्रित करें: Python में वर्चुअल स्क्रीन मॉडल
Python में चाइल्ड PTY चलाकर इंटरैक्टिव ऐप का आउटपुट पार्स करें। टर्मिनल कंट्रोल सीक्वेंस से वर्चुअल स्क्रीन बफर बनाकर किसी भी सेल का ग्लिफ पढ़ना और टेस्टिंग आसान बनाएं.
चाइल्ड PTY के जरिए किसी इंटरैक्टिव प्रोग्राम को नियंत्रित करना और स्क्रीन पर मनचाही पोज़िशन की स्थिति पूछना सुनने में आसान लगता है—जब तक आप इसे Python में करने नहीं लगते। लक्ष्य स्पष्ट है: एक छद्म-टर्मिनल में subprocess शुरू करें, उसका आउटपुट वैसे पढ़ें जैसे कोई टर्मिनल पढ़ता है, वापस कीस्ट्रोक्स भेजें, और किसी भी क्षण पूछ सकें, “पंक्ति r, स्तंभ c पर कौन सा चरित्र है?” पेचीदगी यह है कि टर्मिनल साधारण टेक्स्ट-स्ट्रीम नहीं होते। वे नियंत्रण अनुक्रमों से संचालित स्टेटफुल डिवाइस हैं, और यही पूरे परिदृश्य को बदल देता है।
समस्या सेटअप
इच्छित प्रवाह को एक छोटे स्निपेट में सरल करके देखें। आशय है: PTY में एक प्रोग्राम चलाना, किसी खास स्क्रीन सेल को देखना, थोड़ा इनपुट भेजना, और देखना कि वही सेल अनुरूप रूप से अपडेट होता है।
# संकल्पनात्मक उपयोग
session = create_pty()
session.launch(app_to_run)
session.peek_cell(2, 4)  # '@' अपेक्षित
session.feed_keys("A")
session.peek_cell(2, 4)  # इनपुट से स्क्रीन बदलने के बाद '!' अपेक्षित
असल में बाधा क्या है
स्टैंडर्ड लाइब्रेरी में ऐसा करने के लिए कुछ मौजूद नहीं है। ऑपरेटिंग सिस्टम के PTY बाइट-स्ट्रीम देते हैं। उन्हें टर्मिनल टाइप का कोई ज्ञान नहीं होता; यह समझ कनेक्शन के दूसरी ओर रहती है—किसी फिजिकल टर्मिनल या xterm, putty, MacTerm, cmd, या PowerShell जैसे टर्मिनल एम्युलेटर में। स्क्रीन पर इस समय क्या दिख रहा है, इसका हिसाब रखने के लिए आपको यह कमी खुद पूरी करनी होगी।
इस तक पहुंचने के लिए सबप्रोग्राम के एनवायरनमेंट में उपयुक्त टर्मिनल टाइप होना चाहिए। यह सेट करने के बाद भी उसके आउटपुट को उसी टर्मिनल टाइप के अनुसार व्याख्यायित करना पड़ेगा। यानी टर्मिनल कमांड कोड्स को कर्सर मूवमेंट, अक्षर लिखने, मिटाने और स्क्रॉलिंग में बदलना, और फिर एक इन-मेमोरी स्क्रीन मॉडल बनाए रखना जो वास्तविक टर्मिनल की डिस्प्ले का प्रतिबिंब हो। यह कोई तुच्छ काम नहीं है।
समाधान का खाका
व्यावहारिक तरीका दो हिस्सों में बँटा है। पहला, चाइल्ड प्रोसेस को PTY में शुरू करें और सुनिश्चित करें कि उसे वही टर्मिनल टाइप दिख रहा हो जिसकी आपको जरूरत है। दूसरा, उसके आउटपुट को कैप्चर करें और उसी टर्मिनल टाइप के नियमों से उसका अर्थ निकालें, अपना वर्चुअल स्क्रीन बफर बनाए रखें जिसे आप निर्देशांकों से क्वेरी कर सकें। PTY पर लिखना चाइल्ड प्रोसेस को इनपुट भेजना ही है, जैसे किसी टर्मिनल विंडो में टाइप करना। टर्मिनल इंटरैक्शन डिजाइन करते समय आप curses लाइब्रेरी देखना चाह सकते हैं, लेकिन मूल आवश्यकता वही रहेगी: स्क्रीन की स्थिति ट्रैक करने के लिए सबप्रोग्राम के आउटपुट की व्याख्या करनी होगी।
import os
import pty
def run_in_pty(cmd, argv):
    master_fd, slave_fd = pty.openpty()
    pid = os.fork()
    if pid == 0:
        # चाइल्ड प्रोसेस
        env = os.environ.copy()
        env["TERM"] = "set-proper-term"  # उपयुक्त टर्मिनल प्रकार सुनिश्चित करें
        os.setsid()
        os.dup2(slave_fd, 0)
        os.dup2(slave_fd, 1)
        os.dup2(slave_fd, 2)
        os.close(master_fd)
        os.close(slave_fd)
        os.execvpe(cmd, argv, env)
    else:
        # पैरेंट प्रोसेस
        os.close(slave_fd)
        return pid, master_fd
class DisplayModel:
    def __init__(self, rows=24, cols=80):
        self.rows = rows
        self.cols = cols
        self.grid = [[" "] * cols for _ in range(rows)]
        # वास्तविक इम्प्लीमेंटेशन कर्सर और स्क्रॉल स्थिति भी ट्रैक करेगा
    def ingest(self, data_bytes):
        # data_bytes को टर्मिनल प्रकार के अनुसार व्याख्यायित करें।
        # नियंत्रण अनुक्रमों को कर्सर मूव, लेखन और स्क्रॉलिंग में अनुवाद करें।
        # इसे जानबूझकर लागू नहीं किया गया है; कठिनाई यहीं है।
        pass
    def glyph_at(self, r, c):
        return self.grid[r][c]
def send_keys(fd, text):
    os.write(fd, text.encode())
# उदाहरण के तौर पर वायरिंग (अधूरा; पढ़ने वाला लूप छोड़ा गया):
# pid, pty_fd = run_in_pty("my_program", ["my_program"]) 
# view = DisplayModel()
# ... pty_fd से read() करें और बार-बार view.ingest(...) कॉल करें ...
# send_keys(pty_fd, "A")
# cell = view.glyph_at(2, 4)
यह क्यों मायने रखता है
ऑटोमेशन, स्क्रैपिंग और टर्मिनल ऐप्स की टेस्टिंग में अक्सर हर कीस्ट्रोक के बाद ऑन-स्क्रीन स्थिति पर_assert_ करना पड़ता है। चुने गए टर्मिनल टाइप के हिसाब से आउटपुट की व्याख्या किए बिना “पंक्ति और स्तंभ पर एक खास अक्षर पढ़ना” अविश्वसनीय होगा। आंतरिक PTY कोई स्क्रीन नहीं रखता; वह सिर्फ बाइट्स वहन करता है। अगर आपको स्क्रीन चाहिए, तो उन बाइट्स से वैसी स्क्रीन खुद बनानी होगी जैसी टर्मिनल एम्युलेटर बनाता है।
निष्कर्ष
अगर आपका लक्ष्य चाइल्ड PTY को 2D ग्रिड की तरह रैंडम एक्सेस के साथ क्वेरी करना है, तो दो जिम्मेदारियों की योजना बनाएँ: चाइल्ड को उपयुक्त टर्मिनल टाइप दिखे, और ऐसा लेयर लागू करें जो उसके आउटपुट को वर्चुअल स्क्रीन में बदल दे। इसके लिए स्टैंडर्ड लाइब्रेरी में कोई एक-कॉल समाधान नहीं है, और असली जटिलता उसी व्याख्या-तर्क में है। टर्मिनल UI पर काम करते समय इंटरैक्शन पैटर्न के लिए curses लाइब्रेरी उपयोगी हो सकती है, पर मूल आवश्यकता नहीं बदलती: टर्मिनल कमांड कोड्स को वफादारी से स्क्रीन मॉडल में अनुवाद करें—तभी आप भरोसे से पूछ पाएँगे, “पंक्ति r, स्तंभ c पर कौन सा चरित्र है?”