คู่มือใช้งาน Python subprocess: เรียกใช้คำสั่งระบบและจัดการโพรเซสแบบมืออาชีพ

1. โมดูล subprocess ของ Python คืออะไร

ภาพรวม

โมดูล subprocess ของ Python เป็นเครื่องมืออันทรงพลังสำหรับเรียกใช้คำสั่งระบบหรือโปรแกรมภายนอกจาก Python โดยตรง การใช้โมดูลนี้ทำให้สามารถจัดการ input/output มาตรฐานและจัดการโพรเซสได้ง่าย ช่วยให้โปรแกรม Python เชื่อมต่อกับโปรแกรมอื่นๆ ได้สะดวกขึ้น โดยเป็นวิธีที่ปลอดภัยและยืดหยุ่นกว่า os.system() หรือ commands ที่เคยใช้มาก่อน

การใช้งานหลัก

  • รันคำสั่ง Shell: เรียกใช้คำสั่งระบบอย่างง่าย
  • จัดการโพรเซส: รันโปรแกรมภายนอกและ redirect input/output
  • ประมวลผลแบบ asynchronous: จัดการงานที่ใช้เวลานานหรือรันหลายงานพร้อมกัน

2. วิธีใช้พื้นฐาน: subprocess.run()

ตัวอย่างการใช้งานพื้นฐาน

subprocess.run() คือฟังก์ชันที่ใช้สำหรับรันคำสั่งระบบอย่างง่ายจาก Python ตัวอย่างเช่น การแสดงรายการไฟล์ในไดเรกทอรี สามารถใช้โค้ดดังนี้

import subprocess

result = subprocess.run(['ls', '-l'], capture_output=True, text=True)
print(result.stdout)

โค้ดนี้จะรันคำสั่ง ls -l แล้วบันทึกผลลัพธ์ไว้ใน stdout เพื่อใช้งานใน Python โดย capture_output=True จะจับผลลัพธ์ออกมา และ text=True จะให้ผลลัพธ์เป็นข้อความ

การจัดการข้อผิดพลาด

เมื่อใช้ subprocess.run() หากคำสั่งล้มเหลว สามารถใช้ stderr เพื่อดึงข้อความ error ได้ และตรวจสอบผลการทำงานด้วย returncode

result = subprocess.run(['ls', 'nonexistentfile'], capture_output=True, text=True)
if result.returncode != 0:
    print(f"เกิดข้อผิดพลาด: {result.stderr}")

ในตัวอย่างนี้ ถ้าระบุไฟล์ที่ไม่มีอยู่ จะเห็นข้อความ error ผ่าน standard error

3. การประมวลผลแบบ asynchronous: subprocess.Popen()

การประมวลผลแบบ asynchronous ด้วย Popen

subprocess.run() จะรันแบบ synchronous (ต้องรอคำสั่งจบก่อนถึงไปต่อ) แต่ถ้าใช้ subprocess.Popen() จะสามารถรันโพรเซสแบบ asynchronous คือ ทำงานอย่างอื่นไปพร้อมกันได้

import subprocess

proc = subprocess.Popen(['sleep', '5'], stdout=subprocess.PIPE)
print("เริ่มโปรเซสแล้ว")
proc.wait()
print("โปรเซสเสร็จสิ้นแล้ว")

ในโค้ดนี้ sleep 5 จะรันแบบ background และสามารถทำงานอย่างอื่นในขณะรอได้

การควบคุม input/output มาตรฐาน

Popen สามารถควบคุม redirect input/output ได้ละเอียด เช่น โค้ดต่อไปนี้จะอ่านไฟล์ ส่งผ่าน cat และเขียนผลลัพธ์ไปไฟล์ใหม่

with open('input.txt', 'r') as infile, open('output.txt', 'w') as outfile:
    proc = subprocess.Popen(['cat'], stdin=infile, stdout=outfile)
    proc.wait()

ทำให้สามารถ redirect input/output ระหว่างโปรแกรมกับไฟล์ได้อย่างยืดหยุ่น

4. ตัวอย่างการใช้งาน: สคริปต์อัตโนมัติ

การสำรองไฟล์ (Backup)

subprocess เหมาะกับการจัดการงานระบบหรือ automate task เช่น โค้ดต่อไปนี้จะสำรองไฟล์ไปยังโฟลเดอร์ backup อัตโนมัติ

import subprocess

files_to_backup = ['file1.txt', 'file2.txt', 'file3.txt']
backup_dir = '/backup/directory/'

for file in files_to_backup:
    subprocess.run(['cp', file, backup_dir])

โค้ดนี้จะคัดลอกไฟล์ที่กำหนดไปยังโฟลเดอร์ backup ช่วยให้ตั้งเวลา backup ได้โดยง่าย

ใช้งานกับ CI/CD Pipeline

subprocess ใช้งานได้กับระบบ CI/CD เช่น รันสคริปต์ทดสอบ หรือ deploy อัตโนมัติ เช่น หลังทดสอบผ่านจึง deploy ต่อได้

侍エンジニア塾

5. ความปลอดภัยและแนวทางที่ควรใช้

ความเสี่ยงของ shell=True

shell=True ใช้เมื่อรันคำสั่งผ่าน shell ซึ่งมีความเสี่ยงด้านความปลอดภัยโดยเฉพาะถ้ารับ input จากผู้ใช้โดยตรง อาจเกิด shell injection ได้ ควรใช้ shell=False เป็นหลัก

import subprocess

# วิธีที่แนะนำ (ปลอดภัย)
subprocess.run(['ls', '-l'])

# shell=True (ต้องระวัง)
subprocess.run('ls -l', shell=True)

รองรับหลายระบบปฏิบัติการ

คำสั่งระบบจะแตกต่างกันในแต่ละ OS สามารถใช้ platform เพื่อตรวจสอบ OS และรันคำสั่งที่เหมาะสม

import platform
import subprocess

if platform.system() == "Windows":
    subprocess.run(['dir'], shell=True)
else:
    subprocess.run(['ls', '-l'])

6. แก้ไขปัญหาและ debug

ข้อผิดพลาดที่พบบ่อยและแนวทางแก้

เมื่อใช้ subprocess อาจเจอ error เช่น ไม่พบไฟล์หรือสิทธิ์ไม่พอ สามารถตรวจสอบ stderr และ returncode เพื่อดูรายละเอียดข้อผิดพลาด

เคล็ดลับสำหรับ debug

ถ้าใส่ check=True จะให้ raise exception เมื่อคำสั่งล้มเหลว สามารถจับ exception และเก็บ log ได้ง่ายขึ้น

import subprocess

try:
    result = subprocess.run(['ls', '-l'], check=True, capture_output=True, text=True)
    print(result.stdout)
except subprocess.CalledProcessError as e:
    print(f"เกิดข้อผิดพลาด: {e}")

7. ประมวลผลแบบ asynchronous กับ asyncio

การประมวลผลแบบ asynchronous ด้วย asyncio

เมื่อใช้ asyncio ร่วมกับ subprocess จะสามารถจัดการงานหลายอย่างพร้อมกันแบบ async ได้ ตัวอย่างต่อไปนี้ใช้ asyncio รัน ls แบบ async และรับค่าผลลัพธ์

import asyncio
import subprocess

async def run_command():
    proc = await asyncio.create_subprocess_exec('ls', '-l',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)

    stdout, stderr = await proc.communicate()

    if stdout:
        print(f'[stdout]n{stdout.decode()}')
    if stderr:
        print(f'[stderr]n{stderr.decode()}')

asyncio.run(run_command())

โค้ดนี้จะรันคำสั่งแบบ async และดึงผลลัพธ์มาตรวจสอบได้ ช่วยให้บริหารงาน async ได้มีประสิทธิภาพยิ่งขึ้น

侍エンジニア塾