Fix subprocess.CalledProcessError: Command Returned Non-Zero Exit Status trong Python

intermediate🐍 Python2026-03-26| Python 3.x, Linux / macOS / Windows, thực thi lệnh shell qua module subprocess

Error Message

subprocess.CalledProcessError: Command '['...']' returned non-zero exit status 1
#python#subprocess#shell#process

Lỗi Gặp Phải

Bạn gọi một lệnh shell từ Python bằng subprocess.run(), subprocess.check_call(), hoặc subprocess.check_output() và nhận được:

subprocess.CalledProcessError: Command '['git', 'push']' returned non-zero exit status 1

Lỗi này xảy ra khi lệnh thoát với mã trạng thái khác 0 — tức là thất bại. Python chỉ ném ra CalledProcessError khi bạn yêu cầu nó kiểm tra lỗi: thông qua check=True, hoặc dùng check_call/check_output.

Nguyên Nhân

Mỗi tiến trình khi kết thúc đều trả về một mã trạng thái. Bằng 0 nghĩa là thành công; khác 0 nghĩa là thất bại. Mặc định, subprocess.run() trả về đối tượng kết quả ngay cả khi lệnh thất bại — không có exception nào được ném ra. Khi truyền check=True, Python sẽ chuyển mã thoát khác 0 thành CalledProcessError.

Các nguyên nhân thường gặp:

  • Lệnh thực thi thất bại (ví dụ: git push bị từ chối, pip install không tìm thấy gói)
  • Truyền sai tham số cho lệnh
  • Thiếu dependency hoặc binary không có trong PATH
  • Bị từ chối quyền truy cập file hoặc thư mục
  • Script gọi tường minh sys.exit(1) hoặc exit 1

Bước 1 — Đọc Output Lỗi Thực Tế

Trước hết: hãy đọc những gì lệnh in ra. Subprocess mặc định không capture gì cả — output đổ thẳng ra terminal rồi biến mất. Hãy capture như sau:

import subprocess

result = subprocess.run(
    ['git', 'push'],
    capture_output=True,
    text=True
)
print('STDOUT:', result.stdout)
print('STDERR:', result.stderr)
print('Exit code:', result.returncode)

Chín trên mười trường hợp, thông báo lỗi thực sự nằm trong stderr. Đọc nó — hầu hết các cách sửa sẽ trở nên rõ ràng ngay lúc này.

Bước 2 — Bắt Exception Đúng Cách

Khi dùng check=True, hãy bao lệnh gọi trong try/except để xử lý lỗi mà không làm crash chương trình:

import subprocess

try:
    result = subprocess.run(
        ['git', 'push'],
        check=True,
        capture_output=True,
        text=True
    )
    print(result.stdout)
except subprocess.CalledProcessError as e:
    print(f'Command failed with exit code {e.returncode}')
    print(f'STDOUT: {e.stdout}')
    print(f'STDERR: {e.stderr}')

Đối tượng exception e chứa .returncode, .stdout, và .stderr. Ba thuộc tính này cho bạn biết chính xác điều gì đã xảy ra.

Bước 3 — Sửa Dựa Trên Nguyên Nhân Gốc

Không tìm thấy lệnh

Stderr hiển thị command not found hoặc No such file or directory? Binary không có trong PATH. Kiểm tra trước khi chạy:

import shutil

if not shutil.which('mycommand'):
    raise EnvironmentError('mycommand is not installed or not in PATH')

Hoặc bỏ qua việc tìm kiếm và dùng đường dẫn đầy đủ:

subprocess.run(['/usr/local/bin/mycommand', '--flag'], check=True)

Các tính năng shell (pipe, redirect, wildcard)

Truyền danh sách như ['ls', '-la', '|', 'grep', 'txt'] sẽ không hoạt động — ký tự pipe được coi là tham số thông thường, không phải toán tử shell. Dùng shell=True với chuỗi thay thế:

# Sai — | không phải là tham số
subprocess.run(['ls', '-la', '|', 'grep', 'txt'], check=True)

# Đúng
subprocess.run('ls -la | grep txt', shell=True, check=True)

Một quy tắc bắt buộc: không bao giờ truyền chuỗi do người dùng cung cấp vào shell=True. Đó là lỗ hổng shell injection đang chờ xảy ra. Chỉ dùng chuỗi hardcode hoặc input đã được kiểm tra.

Vấn đề thư mục làm việc

Các lệnh như npm install hoặc make cần chạy từ một thư mục cụ thể. Dùng cwd thay vì os.chdir():

subprocess.run(['npm', 'install'], check=True, cwd='/path/to/project')

Thiếu biến môi trường

Một số script phụ thuộc vào các biến môi trường chưa được thiết lập trong môi trường subprocess. Hãy truyền chúng tường minh:

import os

env = os.environ.copy()
env['MY_VAR'] = 'value'
subprocess.run(['myscript.sh'], check=True, env=env)

Bị từ chối quyền thực thi

Script chưa có quyền thực thi? Có hai cách xử lý. Hoặc thêm bit execute trước:

subprocess.run(['chmod', '+x', 'myscript.sh'], check=True)
subprocess.run(['./myscript.sh'], check=True)

Hoặc bỏ qua vấn đề quyền và gọi trình thông dịch trực tiếp:

subprocess.run(['bash', 'myscript.sh'], check=True)

Bước 4 — Quyết Định: check=True hay Kiểm Tra Thủ Công?

Không phải mọi thất bại đều cần ném exception. Khi mã thoát khác 0 là kết quả bình thường — kiểm tra kết nối mạng, kiểm tra xem tiến trình có đang chạy không — hãy kiểm tra returncode trực tiếp:

result = subprocess.run(['ping', '-c', '1', '8.8.8.8'], capture_output=True)
if result.returncode == 0:
    print('Host is reachable')
else:
    print('Host unreachable')

Dùng check=True cho các lệnh mà thất bại cần dừng thực thi. Bỏ qua nó khi thất bại là một kết quả hợp lệ.

Xác Nhận Đã Sửa Xong

Chạy lệnh gọi riêng lẻ và xác nhận mã thoát trả về là 0:

result = subprocess.run(
    ['your', 'command', 'here'],
    capture_output=True,
    text=True
)
print('Exit code:', result.returncode)  # Should print: Exit code: 0
print(result.stdout)

Mã thoát 0 — bạn đã xong. Vẫn khác 0 — kiểm tra result.stderr để tìm manh mối tiếp theo và lặp lại.

Tóm Tắt Nhanh

  • Capture stderr trước tiêncapture_output=True, text=True tiết lộ nguyên nhân thất bại thực sự
  • Dùng check=True cho các lệnh quan trọng khi thất bại cần dừng thực thi
  • Bỏ check=True khi mã thoát khác 0 là kết quả dự kiến, hợp lệ
  • Không bao giờ kết hợp shell=True với input không đáng tin cậy — thay vào đó truyền danh sách tham số
  • Kiểm tra lệnh trong terminal trước để loại trừ các vấn đề môi trường và PATH

Related Error Notes