Hướng dẫn sử dụng Pexpect để tự động hóa dòng lệnh trong Python

Pexpect là một thư viện Python thuần túy được thiết kế để sinh ra các tiến trình con (child applications) và điều khiển chúng tự động. Đây là công cụ lý tưởng để quản lý các ứng dụng tương tác như ssh, ftp, telnet, hay các công cụ yêu cầu nhập mật khẩu. Không giống như Expect gốc phụ thuộc vào TCL, Pexpect hoạt động hoàn toàn bằng Python và không cần cài đặt các thư viện C hay mô-đun mở rộng nào khác. Nó hoạt động tốt trên mọi nền tảng hỗ trợ mô-đun pty chuẩn.

Giao diện chính: run() và spawn

Pexpect cung cấp hai cách thức chính để tương tác: hàm run() và lớp spawn. Hàm run() là giải pháp thay thế tiện lợi cho os.system(), cho phép thực thi một lệnh và nhận về kết quả đầu ra ngay lập tức. Nó phù hợp với các tác vụ nhanh, đơn giản.

import pexpect

# Thực thi lệnh 'ls -l' và lấy kết quả
output = pexpect.run('ls -l /etc')
print(output.decode())

Đối với các tác vụ phức tạp hơn, lớp spawn là lựa chọn mạnh mẽ hơn. Nó cho phép bạn tạo một tiến trình con và giao tiếp hai chiều bằng cách gửi dữ liệu đầu vào và chờ đợi các mẫu (pattern) cụ thể trong đầu ra.

import pexpect

# Tạo kết nối SSH
process = pexpect.spawn('ssh user@192.168.1.10')

# Chờ đợi nhắc nhập mật khẩu
process.expect('password:')

# Gửi mật khẩu và xuống dòng
process.sendline('my_secret_password')

Bạn cũng có thể sử dụng trình quản lý ngữ cảnh (context manager) để đảm bảo tài nguyên được giải phóng:

with pexpect.spawn('scp data.txt user@host:/tmp') as sess:
    sess.expect('password:')
    sess.sendline('mypassword')

Cơ chế khớp mẫu với expect()

Phương thức expect() được sử dụng để chờ đợi tiến trình con xuất dữ liệu khớp với một mẫu xác định. Mẫu này có thể là một chuỗi, một biểu thức chính quy (regex) đã được biên dịch, hoặc danh sách các mẫu. Phương thức này trả về chỉ số của mẫu khớp trong danh sách.

Nếu mẫu là một danh sách, Pexpect sẽ chọn mẫu khớp đầu tiên xuất hiện trong luồng dữ liệu. Nếu nhiều mẫu khớp cùng lúc, mẫu nằm bên trái nhất trong danh sách sẽ được ưu tiên. Điều quan trọng cần lưu ý là dữ liệu đến theo các khối (chunks) khác nhau, do đó việc xác định mẫu khớp có thể bị ảnh hưởng bởi tốc độ truyền tải.

Các thuộc tính quan trọng sau khi khớp mẫu thành công bao gồm:

  • match: Đối tượng kết quả khớp (re.MatchObject).
  • before: Chuỗi dữ liệu nhận được trước khi khớp mẫu.
  • after: Chuỗi dữ liệu khớp với mẫu.

Xử lý EOF và TIMEOUT

Thay vì sử dụng khối try-except để bắt các ngoại lệ EOF (kết thúc tập tin) hoặc TIMEOUT, bạn có thể thêm chúng vào danh sách mẫu. Cách tiếp cận này giúp mã nguồn gọn gàng và dễ quản lý luồng hơn.

# Định nghĩa danh sách các trạng thái mong đợi
patterns = ['success', 'error', pexpect.EOF, pexpect.TIMEOUT]

index = process.expect(patterns)

if index == 0:
    print("Thao tác thành công!")
elif index == 1:
    print("Gặp lỗi hệ thống.")
elif index == 2:
    print("Tiến trình đã kết thúc bất ngờ.")
elif index == 3:
    print("Đã hết thời gian chờ.")

Chế độ tương tác với interact()

Phương thức interact() chuyển quyền điều khiển tiến trình con cho người dùng thực (thông qua bàn phím và màn hình). Điều này hữu ích khi bạn muốn tự động hóa phần đầu của quá trình kết nối (như nhập mật khẩu) và sau đó để người dùng thao tác thủ công.

Mặc định, phím thoát (escape character) là Ctrl-]. Khi người dùng nhấn phím này, quyền điều khiển sẽ trả lại cho script Python. Bạn có thể tắt tính năng này bằng cách đặt escape_character=None.

process = pexpect.spawn('telnet localhost')
process.expect('login:')
process.sendline('admin')
process.expect('Password:')
process.sendline('admin')

# Chuyển giao quyền điều khiển cho người dùng
process.interact()

Chi tiết về lớp Spawn và cấu hình

Tham số lệnh và Shell

Khởi tạo spawn có thể nhận lệnh dưới dạng chuỗi hoặc danh sách. Lưu ý rằng Pexpect không tự động xử lý các ký tự đặc biệt của shell như |, >, hay *. Nếu bạn cần sử dụng pipe hay redirection, bạn phải gọi shell một cách rõ ràng.

# Sử dụng danh sách tham số (khuyến nghị)
cmd = pexpect.spawn('/bin/ls', ['-l', '/tmp'])

# Sử dụng chuỗi (chỉ chạy lệnh đơn)
cmd = pexpect.spawn('ls -l /tmp')

# Thực thi lệnh pipe phức tạp qua shell
cmd = pexpect.spawn('/bin/bash', ['-c', 'ls -l | grep py'])

Logging và Ghi nhật ký

Để gỡ lỗi (debug), bạn có thể ghi lại toàn bộ dữ liệu gửi và nhận thông qua thuộc tính logfile. Bạn có thể ghi ra file hoặc chuẩn đầu ra sys.stdout.

import sys

# Ghi log ra màn hình console (Python 3)
process = pexpect.spawn('ssh user@host', encoding='utf-8')
process.logfile = sys.stdout

# Hoặc ghi ra file riêng biệt
with open('debug_log.txt', 'wb') as f:
    process.logfile = f

Nếu chỉ muốn ghi dữ liệu nhận được (read) hoặc gửi đi (send), hãy sử dụng logfile_read hoặc logfile_send.

Độ trễ và xử lý mật khẩu

Một vấn đề phổ biến khi tự động hóa nhập mật khẩu là mật khẩu bị hiển thị (echo) ra màn hình. Điều này xảy ra khi script gửi mật khẩu quá nhanh trước khi ứng dụng con kịp tắt chế độ hiển thị. Pexpect giải quyết việc này bằng delaybeforesend, mặc định là 0.05 giây.

process = pexpect.spawn('ftp ftp.example.com')
process.expect('Name .*: ')
process.sendline('anonymous')

# Tăng độ trễ trước khi gửi mật khẩu để tránh bị hiện mật khẩu
process.delaybeforesend = 1
process.expect('Password:')
process.sendline('guest@example.com')

Trạng thái thoát

Để biết tiến trình con đã kết thúc như thế nào, bạn cần gọi phương thức close(). Sau đó, kiểm tra exitstatus (mã thoát) hoặc signalstatus (nếu bị tín hiệu kết thúc).

proc = pexpect.spawn('some_command')
proc.expect(pexpect.EOF)
proc.close()

if proc.exitstatus == 0:
    print("Chạy thành công")
else:
    print(f"Thất bại với mã: {proc.exitstatus}")

Ví dụ nâng cao: Tương tác với Python REPL

Đoạn mã dưới đây minh họa việc khởi tạo một trình thông dịch Python, gửi mã lệnh, xử lý đầu ra và sử dụng interact để bàn giao quyền điều khiển.

import pexpect
import sys

def run_python_interaction():
    # Khởi tạo Python REPL với mã hóa utf-8
    py_proc = pexpect.spawn('python3', encoding='utf-8')

    # Chờ đợi dấu nhắc lệnh đầu tiên
    py_proc.expect('>>>')
    
    # Gửi một lệnh tính toán đơn giản
    py_proc.sendline('import math')
    py_proc.expect('>>>')
    
    py_proc.sendline('print(math.sqrt(16))')
    py_proc.expect('>>>')
    
    # In kết quả trước khi lệnh tiếp theo (before chứa output của lệnh trước)
    print("Kết quả từ tiến trình con:")
    print(py_proc.before)

    # Chuyển sang chế độ tương tác cho người dùng
    print("Đang chuyển sang chế độ tương tác...")
    py_proc.interact(escape_character='^]')
    
    # Sau khi người dùng thoát khỏi interact, quay lại script
    print("Đã kết thúc phiên tương tác.")
    py_proc.close()

if __name__ == "__main__":
    run_python_interaction()

Thông qua việc sử dụng pexpect, các nhà phát triển có thể xây dựng các kịch bản tự động hóa mạnh mẽ để kiểm thử phần mềm, quản trị hệ thống từ xa, và triển khai ứng dụng một cách hiệu quả.

Thẻ: python pexpect automation ssh system-administration

Đăng vào ngày 22 tháng 5 lúc 01:23