---
id: programming-python
title: "Python cho script vận hành"
shortTitle: "Python"
track: programming
trackTitle: "Programming cho DevOps"
order: 1
level: core
status: published
estimatedMinutes: 45
prerequisites:
  - linux-shell-scripting
next: programming-nodejs
reviewedAgainst: 2026-10-12
sources:
  - title: "Status of Python versions"
    url: https://devguide.python.org/versions/
  - title: "venv"
    url: https://docs.python.org/3/library/venv.html
  - title: "argparse"
    url: https://docs.python.org/3/library/argparse.html
  - title: "subprocess"
    url: https://docs.python.org/3/library/subprocess.html
  - title: "pathlib"
    url: https://docs.python.org/3/library/pathlib.html
---

# Python cho script vận hành

Lab dùng Python 3.14. Theo [devguide](https://devguide.python.org/versions/), nhánh 3.14 đang ở giai đoạn bugfix tới tháng 10/2030. 3.15.0 ra ngày 2026-10-09, còn mới. Code trong bài chạy được từ 3.11. Lệnh giả định Linux hoặc WSL2.

## Để làm gì

Viết một script có argument, đọc file JSON, gọi lệnh ngoài, và trả exit code khác 0 khi có lỗi. CI và cron dựa vào exit code đó. Bash làm được việc này, nhưng script dài hơn khoảng 30 dòng, có nhiều nhánh, thì Python đỡ rối hơn.

## Gồm những gì

- `venv` để không cài package lên Python của hệ thống.
- `argparse` cho CLI.
- `pathlib` và `json` để đọc file.
- `subprocess.run` với list argument, `check=True`, `text=True`, `timeout`.

Bài này chỉ dùng standard library. Phần gọi HTTP mình để sang bài Node.js, còn build binary một file thì để bài Go. Ruby chỉ ở mức nên biết: bạn vẫn sẽ gặp nó trong Chef hoặc Puppet đời cũ, nhưng không cần học sâu.

## Dùng khi nào

Chọn Python khi script phải parse JSON, có vài flag, hoặc bạn muốn test từng hàm. Nếu việc cần làm chỉ là năm dòng `grep` với `awk` thì cứ để trong Bash, mở Python ra chỉ thêm rườm rà.

Đừng gọi `subprocess` với `shell=True` và một chuỗi ghép từ input. Docs subprocess ghi rõ: mặc định library này không mở shell. Khi bật `shell=True`, bạn phải tự quote metacharacter; quên một chỗ là dính shell injection.

## Kiến thức cốt lõi

`python3 -m venv .venv` tạo một thư mục có interpreter riêng. Cài package sau này nằm trong đó, không đụng Python mà apt đã cài cho tool khác.

`argparse` khai báo argument một lần, rồi tự sinh `--help` và báo lỗi khi thiếu argument bắt buộc. `main()` trả số. `sys.exit` nhận số đó làm exit code của process. Trả `0` là thành công. Khác `0` là lỗi. CI coi mọi mã khác 0 là fail.

`subprocess.run(["git", "status"])` chờ lệnh xong và trả `CompletedProcess`. `returncode` là exit code của lệnh con.

| Argument | Tác dụng |
| --- | --- |
| `check=True` | Mã khác 0 thì ném `CalledProcessError` |
| `capture_output=True` | Giữ stdout và stderr, không in ra terminal |
| `text=True` | stdout, stderr là `str`, không phải `bytes` |
| `timeout=10` | Quá 10 giây thì giết process con và ném `TimeoutExpired` |

Không truyền `capture_output` thì output của lệnh con chảy thẳng ra terminal. Hợp khi bạn muốn người chạy script nhìn thấy log.

## Lệnh và code mẫu

```bash
mkdir -p "$HOME/devops-lab/programming"
cd "$HOME/devops-lab/programming"
python3 -m venv .venv
source .venv/bin/activate
python --version
```

`services.json`:

```json
{
  "services": [
    { "name": "api", "image": "api:1.4.2" },
    { "name": "worker", "image": "" }
  ]
}
```

`check_services.py`:

```python
#!/usr/bin/env python3
import argparse
import json
import sys
from pathlib import Path


def main() -> int:
    parser = argparse.ArgumentParser(description="Check service manifest")
    parser.add_argument("manifest")
    parser.add_argument("--strict", action="store_true")
    args = parser.parse_args()

    data = json.loads(Path(args.manifest).read_text(encoding="utf-8"))
    missing = [item["name"] for item in data["services"] if not item.get("image")]
    if missing and args.strict:
        print("missing image: " + ", ".join(missing), file=sys.stderr)
        return 1
    print(f"{len(data['services'])} services, missing image: {len(missing)}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
```

Gọi lệnh ngoài, không qua shell:

```python
import subprocess

result = subprocess.run(
    ["git", "--version"],
    check=True,
    capture_output=True,
    text=True,
    timeout=10,
)
print(result.stdout.strip())
```

## Thực hành

1. Tạo lab, venv, và hai file ở trên.
2. `python check_services.py --help` để xem argparse tự sinh help.
3. Chạy không có `--strict`. Exit code là 0. `echo $?` in `0`.
4. Chạy với `--strict`. Script in ra stderr và `echo $?` in `1`.
5. Thêm khối `subprocess.run` vào cuối `main`, trước `return 0`, rồi chạy lại trong thư mục không phải git repo. `git --version` vẫn chạy được vì không cần repo.
6. Đổi list thành `subprocess.run("git --version", shell=True, ...)` rồi đổi lại về list. Giữ bản list.

## Kết quả mong đợi

- Không có `--strict`: in `2 services, missing image: 1`, exit code 0.
- Có `--strict`: stderr có `missing image: worker`, exit code 1.
- `git --version` in một dòng, không có traceback.
- `.venv` không bị commit. Thêm `.venv/` vào `.gitignore` nếu thư mục này nằm trong repo.

## Checklist

- [ ] Tạo venv bằng `python3 -m venv`, không cài package lên Python hệ thống.
- [ ] Để `main` trả int và gọi `sys.exit`.
- [ ] Gọi lệnh ngoài bằng list argument.
- [ ] Bật `check=True` khi mã khác 0 phải làm script dừng.
- [ ] Đặt `timeout` cho lệnh có thể treo.
- [ ] Giải thích vì sao không dùng `shell=True` với input từ ngoài.

## Lỗi thường gặp

**`subprocess.run("git status")` không có `shell=True`.** Trên POSIX, một string được coi là tên executable, không phải một dòng shell. `git status` có dấu cách nên không tìm thấy file. Truyền `["git", "status"]`.

**Quên `text=True` rồi gọi `.split()` trên stdout.** stdout là `bytes`. Hoặc thêm `text=True`, hoặc `.decode()`.

**Bắt `CalledProcessError` rồi im.** Lệnh fail mà script vẫn exit 0, CI xanh. Bắt lỗi thì in stderr và `return 1`, hoặc đừng bắt.

**Cài `requests` vào Python của Ubuntu.** Package bạn cài tay có thể đụng với bản apt quản lý, và lần `apt upgrade` sau dễ sinh lỗi khó hiểu. Script cũng chỉ chạy trên máy bạn, vì máy đồng nghiệp không có package đó. Hãy dùng venv. Bài này chưa cần package ngoài.

## Nguồn

1. [Status of Python versions](https://devguide.python.org/versions/) — Python devguide, nhánh nào đang bugfix, security, hay hết hạn.
2. [venv](https://docs.python.org/3/library/venv.html) — tạo môi trường riêng.
3. [argparse](https://docs.python.org/3/library/argparse.html) — argument, `--help`, exit code khi thiếu argument.
4. [subprocess](https://docs.python.org/3/library/subprocess.html) — `run`, `check`, `capture_output`, `text`, và mục Security Considerations về `shell=True`.
5. [pathlib](https://docs.python.org/3/library/pathlib.html) — `Path.read_text`.

## Tiếp theo

[Node.js cho CLI nội bộ](./02-nodejs.md)
