Python Advance 📂 Advance topics · 3 of 3 324 min read

Python Subprocess — Running External Commands

Master Python's subprocess module — the standard way to run external commands, capture their output, chain them with pipes, and orchestrate CLI tools from Python. Covers subprocess.run for the 90% case, Popen for streaming and long-running processes, pipes for chaining commands like ps | grep | wc, timeouts, error handling, security around shell=True, and practical DevOps examples with runnable code.

🌐
SectionSection第 01 节

The Story That Explains subprocess 💈subprocess समझाने वाली कहानी 💈一个讲清 subprocess 的故事 💈

Hiring a Specialist Instead of Doing It Yourselfख़ुद करने की जगह विशेषज्ञ को बुलाना请专业人士,而不是自己动手
You need a haircut. You could learn hairdressing, buy scissors and cut your own hair — or you could walk into a salon, ask the barber, wait, and collect the result. The barber is a separate person with separate tools. You give instructions, the barber works on their own, and then hands back what you asked for.

That is exactly what Python's subprocess module does. Instead of rewriting git, ffmpeg, ls, ping or any other program in Python, you start a new operating-system process that runs the real tool, collect what it prints, and carry on.
आपको बाल कटवाने हैं। आप बाल काटना सीख सकते हैं, कैंची ख़रीद सकते हैं और ख़ुद काट सकते हैं — या आप salon जाएँ, नाई से कहें, इंतज़ार करें और नतीजा ले लें। नाई अलग इंसान है, उसके औज़ार अलग हैं। आप निर्देश देते हैं, नाई अपने आप काम करता है, और फिर आपको वही लौटाता है जो आपने माँगा।

Python का subprocess module ठीक यही करता है। git, ffmpeg, ls, ping या किसी दूसरे program को Python में दोबारा लिखने की जगह, आप एक नया operating-system process शुरू करते हैं जो असली tool चलाता है, उसका print किया हुआ लेते हैं, और आगे बढ़ते हैं।
你需要理发。你可以去学理发、买剪刀、自己剪 — 也可以 走进理发店,请理发师来剪,等一会儿,取走结果。理发师是另一个人,用的是自己的工具。你给出要求,理发师独立完成,然后把你要的结果交给你。

这正是 Python 的 subprocess 模块做的事。你不必用 Python 重写 git、ffmpeg、ls、ping 或其他任何程序,而是 启动一个新的操作系统进程 来运行真正的工具,收集它打印的内容,然后继续干你的事。

Every operating system runs work as processes. When your script needs to run ls -la, convert a video with ffmpeg, call git status or start any other program, it uses subprocess to start a child process, give it the command, and talk to it through three streams: stdin, stdout and stderr.

हर operating system काम को processes के रूप में चलाता है। जब आपकी script को ls -la चलाना हो, ffmpeg से video बदलना हो, git status बुलाना हो या कोई और program शुरू करना हो, तो वह subprocess से child process शुरू करती है, उसे command देती है, और तीन streams से उससे बात करती है: stdin, stdout और stderr।

每个操作系统都以 进程 的形式运行任务。当你的脚本需要运行 ls -la、用 ffmpeg 转换视频、调用 git status 或启动其他任何程序时,它会用 subprocess 启动一个子进程,把命令交给它,并通过三个流与它通信:stdin、stdout 和 stderr。

💡
The Core Insightमूल बात核心要点

subprocess is the recommended way to run other programs from Python. It replaces older tools like os.system() and gives you full control over arguments, streams, timeouts, environment variables, working directory and exit codes — all in one consistent API.

Python से दूसरे programs चलाने का सुझाया गया तरीक़ा subprocess है। यह os.system() जैसे पुराने औज़ारों की जगह लेता है और arguments, streams, timeouts, environment variables, working directory और exit codes पर पूरा नियंत्रण देता है — सब एक ही API में।

subprocess 是在 Python 中运行其他程序的推荐方式。它取代了 os.system() 等旧工具,让你能统一地完全控制参数、数据流、超时、环境变量、工作目录和退出码。

🔧
Fixed: Which Old Functions It Replacesसुधार: यह कौन-से पुराने Functions बदलता है已修正:它取代的是哪些旧函数

The original called os.system(), os.popen() and commands.getoutput() "older, unsafe functions". Small corrections: commands existed only in Python 2 — it is gone in Python 3. os.system() and os.popen() still exist; os.popen() is now built on top of subprocess. They are not unsafe by themselves — they always use the shell, so they become unsafe when you mix user input into the command (Section 10).

मूल में os.system(), os.popen() और commands.getoutput() को "पुराने, असुरक्षित functions" कहा गया था। छोटे सुधार: commands सिर्फ़ Python 2 में था — Python 3 में नहीं है। os.system() और os.popen() अब भी हैं; os.popen() अब subprocess पर ही बना है। ये अपने आप में असुरक्षित नहीं — ये हमेशा shell इस्तेमाल करते हैं, इसलिए command में user input मिलाने पर असुरक्षित बनते हैं (Section 10)।

原文把 os.system()、os.popen() 和 commands.getoutput() 称为"旧的、不安全的函数"。小修正:commands 只存在于 Python 2,Python 3 已经没有了。os.system() 和 os.popen() 仍然存在;os.popen() 现在就是基于 subprocess 实现的。它们本身并非不安全 — 它们总是经过 shell,所以一旦把用户输入拼进命令就会变得不安全(第 10 节)。

▶️
Seven Simulators, Real Runsसात Simulators, असली Runs七个模拟器,全部真实运行

Look for the interactive boxes: the run() Lab (Section 05), Streaming Lab (07), Pipeline Flow (08), Deadlock Lab (09), Injection Lab (10), Shutdown Lab (12) and the Bug Doctor (14). Every output and timing in them was recorded from real runs on Linux.

Interactive डिब्बे खोजें: run() Lab (Section 05), Streaming Lab (07), Pipeline Flow (08), Deadlock Lab (09), Injection Lab (10), Shutdown Lab (12) और Bug Doctor (14)। इनका हर output और timing Linux पर असली runs से रिकॉर्ड किया गया है।

请留意这些互动框:run() 实验室(第 05 节)、流式输出实验室(07)、管道流程(08)、死锁实验室(09)、注入实验室(10)、关闭实验室(12)和 Bug 医生(14)。其中所有输出和计时都来自 Linux 上的真实运行。


SectionSection第 02 节

The Foundation — What Actually Happens 🧱नींव — असल में क्या होता है 🧱基础 — 实际发生了什么 🧱

When you call subprocess.run(["ls", "-la"]), Python asks the operating system to create a new process, connect its streams, run the program and report back how it ended:

जब आप subprocess.run(["ls", "-la"]) बुलाते हैं, तो Python operating system से नया process बनाने, उसकी streams जोड़ने, program चलाने और यह बताने को कहता है कि वह कैसे ख़त्म हुआ:

当你调用 subprocess.run(["ls", "-la"]) 时,Python 请求操作系统创建一个新进程、连接它的数据流、运行程序,并报告它是如何结束的:

Figure 1 — Parent & Child Processचित्र 1 — Parent और Child Process图 1 — 父进程与子进程
Parent and child process connected by three streamsPARENT PROCESSPARENT PROCESS父进程python your_script.pysubprocess.run([...])waits for the childchild का इंतज़ार करता है等待子进程reads its outputउसका output पढ़ता है读取它的输出gets the exit codeexit code लेता है拿到退出码CHILD PROCESSCHILD PROCESS子进程ls -lastdin (0)stdout (1)stderr (2)ends with an exit codeexit code के साथ ख़त्म होता है以退出码结束inputoutputerrorsOPERATING SYSTEM — creates the child, connects the pipes, reports the exit codeOPERATING SYSTEM — child बनाता है, pipes जोड़ता है, exit code बताता है操作系统 — 创建子进程、连接管道、报告退出码Two separate processes, connected by three streamsदो अलग processes, तीन streams से जुड़े两个独立的进程,通过三个流相连

The child has its own memory and its own process ID. The only links back to Python are three streams — stdin (0), stdout (1), stderr (2) — plus the exit code.

Child की अपनी memory और अपनी process ID होती है। Python तक लौटने के रास्ते बस तीन streams — stdin (0), stdout (1), stderr (2) — और exit code हैं।

子进程有自己的内存和自己的进程 ID。它与 Python 之间仅有的联系是三个流 — stdin (0)、stdout (1)、stderr (2) — 再加上退出码。

🔑
Three Streams You Will See Everywhereतीन Streams जो हर जगह दिखेंगी随处可见的三个流

stdin is what the child reads. stdout is its normal output. stderr is for errors and messages. On Unix they are file descriptors 0, 1 and 2. Every subprocess option in Python is about one of these three.

stdin वह है जो child पढ़ता है। stdout उसका सामान्य output है। stderr errors और संदेशों के लिए है। Unix पर ये file descriptors 0, 1 और 2 हैं। Python का हर subprocess option इन्हीं तीन में से किसी एक के बारे में है।

stdin 是子进程 读取 的内容。stdout 是正常输出。stderr 用于错误和提示信息。在 Unix 上它们是文件描述符 0、1、2。Python 中每个 subprocess 选项都与这三者之一有关。

🔧
Fixed: Exit Codes and "Fork"सुधार: Exit Codes और "Fork"已修正:退出码与"fork"

The original said the child "exits with code 0…255" and that Python "forks" it. Two details: (1) In Python, returncode can also be negative: -9 means the child was killed by signal 9 (SIGKILL), -15 by SIGTERM. You will see these in the Shutdown Lab. (2) "Fork" is the Unix picture; on Linux Python may use posix_spawn or vfork for speed, and on Windows it uses CreateProcess. The idea is the same everywhere: a brand-new, separate process.

मूल में कहा गया था कि child "0…255 code के साथ exit करता है" और Python उसे "fork" करता है। दो बातें: (1) Python में returncode negative भी हो सकता है: -9 का मतलब child को signal 9 (SIGKILL) ने मारा, -15 का मतलब SIGTERM ने। ये आप Shutdown Lab में देखेंगे। (2) "Fork" Unix वाली तस्वीर है; Linux पर Python गति के लिए posix_spawn या vfork इस्तेमाल कर सकता है, और Windows पर CreateProcess। विचार हर जगह एक है: बिल्कुल नया, अलग process।

原文说子进程"以 0…255 的退出码结束",并说 Python 会"fork"它。两个细节:(1) 在 Python 中,returncode 也可能是 负数:-9 表示子进程被信号 9(SIGKILL)杀死,-15 表示被 SIGTERM 结束。你会在关闭实验室中看到它们。(2)"fork"是 Unix 的说法;在 Linux 上 Python 可能为了速度使用 posix_spawn 或 vfork,在 Windows 上则用 CreateProcess。各平台的思路相同:一个全新的、独立的进程。


SectionSection第 03 节

subprocess.run — The Everyday Tool 🛠️subprocess.run — रोज़ का औज़ार 🛠️subprocess.run — 日常工具 🛠️

subprocess.run() is your default. It runs a command, waits for it to finish and returns a CompletedProcess object with the exit code and (if you ask) the captured output. Reach for it first.

subprocess.run() आपका default है। यह command चलाता है, उसके ख़त्म होने का इंतज़ार करता है, और exit code और (माँगने पर) पकड़ा गया output लिए एक CompletedProcess object लौटाता है। पहले इसी को चुनिए।

subprocess.run() 是你的默认选择。它运行命令、等待 它结束,然后返回一个 CompletedProcess 对象,里面有退出码以及(如果你要求的话)捕获的输出。首先用它。

import subprocess

# Simplest possible call - just run it, wait, done
result = subprocess.run(["echo", "Hello, Mohit!"])
print(f"return code: {result.returncode}")
OUTPUT
Hello, Mohit! return code: 0

Notice that Hello, Mohit! was printed by the child, straight to the screen — Python never saw that text. To get the output into a variable, capture it:

ध्यान दीजिए, Hello, Mohit! child ने सीधे screen पर print किया — Python ने वह text देखा ही नहीं। Output को variable में लेने के लिए उसे capture कीजिए:

注意,Hello, Mohit! 是 子进程 直接打印到屏幕上的 — Python 根本没看到这段文字。要把输出存进变量,就要捕获它:

Capturing Output — capture_output + textOutput पकड़ना — capture_output + text捕获输出 — capture_output + text

import subprocess

result = subprocess.run(
    ["ls", "-la", "reports"],
    capture_output=True,       # capture stdout + stderr
    text=True                  # decode bytes to str
)

print("--- STDOUT ---")
print(result.stdout)
print("--- STDERR ---")
print(result.stderr or "(none)")
print(f"exit code: {result.returncode}")
OUTPUT
--- STDOUT --- total 20 drwxr-xr-x 2 root root 4096 Jul 16 11:03 . drwx------ 3 root root 4096 Nov 6 05:38 .. -rw-r--r-- 1 root root 21 Jul 16 11:03 feb.csv -rw-r--r-- 1 root root 21 Jul 16 11:03 jan.csv -rw-r--r-- 1 root root 33 Jul 16 11:03 notes.txt --- STDERR --- (none) exit code: 0
📁
A Real Folder, Real Outputअसली Folder, असली Output真实的文件夹,真实的输出

The original listed /tmp and showed made-up lines (and cut them at 200 characters mid-line). Here we list a small sample folder, reports, with three files. The output is from a real run on Linux; your user names, sizes and dates will differ. These examples use Unix commands (ls, cat, sort) — on Windows the commands are different.

मूल में /tmp की सूची थी और बनावटी lines दिखाई गई थीं (जो 200 characters पर बीच में कट जातीं)। यहाँ हम तीन files वाले छोटे sample folder reports की सूची लेते हैं। Output Linux पर असली run का है; आपके user names, sizes और dates अलग होंगे। ये examples Unix commands (ls, cat, sort) इस्तेमाल करते हैं — Windows पर commands अलग हैं।

原文列出的是 /tmp,并给出了虚构的行(而且会在 200 个字符处截断到行中间)。这里我们列出一个只有三个文件的示例文件夹 reports。输出来自 Linux 上的真实运行;你的用户名、大小和日期会不同。这些例子使用 Unix 命令(ls、cat、sort)— Windows 上的命令不一样。

⚠️
Pass a List, Not a StringString नहीं, List दीजिए传列表,不要传字符串

Prefer ["ls", "-la", "reports"] over "ls -la reports". A list goes straight to the program with no shell in between — no splitting surprises, no injection risk, and filenames with spaces just work. A plain string without shell=True does not even run on Linux: Python looks for a program literally named "ls -la reports" (see the Bug Doctor).

"ls -la reports" की जगह ["ls", "-la", "reports"] चुनिए। List बीच में किसी shell के बिना सीधे program तक जाती है — splitting की हैरानी नहीं, injection का ख़तरा नहीं, और spaces वाले filenames बस चल जाते हैं। shell=True के बिना सादी string Linux पर चलती भी नहीं: Python "ls -la reports" नाम का program ढूँढता है (Bug Doctor देखिए)।

优先用 ["ls", "-la", "reports"],而不是 "ls -la reports"。列表会直接交给程序,中间没有 shell — 不会有拆分上的意外,没有注入风险,带空格的文件名也能正常工作。在 Linux 上,不加 shell=True 的普通字符串根本无法运行:Python 会去找一个名字就叫 "ls -la reports" 的程序(见 Bug 医生)。


SectionSection第 04 节

Handling Errors — check=True and returncode 🚦Errors सँभालना — check=True और returncode 🚦处理错误 — check=True 与 returncode 🚦

Every process ends with an exit code: 0 for success, anything else for failure. By default subprocess.run does not raise an error when a command fails — you must check yourself, or pass check=True.

हर process एक exit code के साथ ख़त्म होता है: सफलता के लिए 0, असफलता के लिए कुछ और। Default में command fail होने पर subprocess.run error raise नहीं करता — आपको ख़ुद जाँचना होगा, या check=True देना होगा।

每个进程结束时都有一个 退出码:0 表示成功,其他值表示失败。默认情况下,命令失败时 subprocess.run 不会 抛出异常 — 你必须自己检查,或者传入 check=True。

❌ Without check=Truecheck=True के बिना不用 check=True
Behaviourव्यवहार行为
The command fails quietlyCommand चुपचाप fail होता है命令悄悄失败
Your script carries on as if all is wellScript ऐसे चलती है जैसे सब ठीक हो脚本若无其事地继续运行
You must check .returncode yourself.returncode ख़ुद जाँचना होगा必须自己检查 .returncode
Failures are easy to missFailures आसानी से छूट जाते हैं很容易漏掉失败
✅ With check=Truecheck=True के साथ使用 check=True
Behaviourव्यवहार行为
Raises CalledProcessError on a non-zero exitNon-zero exit पर CalledProcessError raise退出码非零时抛出 CalledProcessError
Fail fast — stops at the first problemFail fast — पहली समस्या पर रुकता है快速失败 — 遇到第一个问题就停
The exception carries stdout, stderr and returncodeException में stdout, stderr और returncode होते हैं异常中带有 stdout、stderr 和 returncode
Recommended for automation scriptsAutomation scripts के लिए सुझाया गया推荐用于自动化脚本
import subprocess

# No check=True: the failure is easy to miss
result = subprocess.run(["cat", "/does/not/exist"], capture_output=True, text=True)
print("script keeps going... returncode =", result.returncode)
OUTPUT
script keeps going... returncode = 1
import subprocess

# Command that will fail - no such file
try:
    result = subprocess.run(
        ["cat", "/does/not/exist"],
        capture_output=True,
        text=True,
        check=True               # raise on non-zero exit
    )
except subprocess.CalledProcessError as e:
    print(f"Command failed with code {e.returncode}")
    print(f"stderr: {e.stderr.strip()}")
    print(f"cmd:    {e.cmd}")
OUTPUT
Command failed with code 1 stderr: cat: /does/not/exist: No such file or directory cmd: ['cat', '/does/not/exist']
🏆
The Safe Default Patternसुरक्षित Default Pattern安全的默认写法

For automation scripts, start with check=True, capture_output=True, text=True. You get fail-fast behaviour, readable strings instead of bytes, and full access to stderr when things go wrong.

Automation scripts के लिए check=True, capture_output=True, text=True से शुरू कीजिए। आपको fail-fast व्यवहार, bytes की जगह पढ़ने लायक strings, और गड़बड़ी होने पर stderr तक पूरी पहुँच मिलती है।

自动化脚本请从 check=True, capture_output=True, text=True 开始。这样你能得到快速失败的行为、可读的字符串(而不是 bytes),并在出错时完整地拿到 stderr。


SectionSection第 05 节

Timeouts — Never Wait Forever ⏱️Timeouts — कभी हमेशा इंतज़ार नहीं ⏱️超时 — 永远不要无限等待 ⏱️

A stuck process will freeze your script for ever. Always set a timeout when you are not sure how long a command will take. Here a small command prints one "tick" per second; we give it 2.5 seconds:

अटका हुआ process आपकी script को हमेशा के लिए रोक देगा। जब पक्का न हो कि command कितना समय लेगा, तो हमेशा timeout दीजिए। यहाँ एक छोटा command हर second एक "tick" print करता है; हम उसे 2.5 seconds देते हैं:

卡住的进程会让你的脚本永远停在那里。只要不确定命令要运行多久,就一定要设置 timeout。这里一个小命令每秒打印一个"tick",我们只给它 2.5 秒:

import subprocess

try:
    result = subprocess.run(
        ["bash", "-c", "for i in 1 2 3 4 5; do echo tick $i; sleep 1; done"],
        capture_output=True,
        text=True,
        timeout=2.5              # seconds - kill after this
    )
    print(result.stdout)
except subprocess.TimeoutExpired as e:
    print(f"Timed out after {e.timeout}s")
    # e.stdout is ALWAYS bytes here, even with text=True
    partial = e.stdout.decode() if e.stdout else "none"
    print("Partial output:")
    print(partial, end="")
OUTPUT
Timed out after 2.5s Partial output: tick 1 tick 2 tick 3
🔧
Fixed: e.stdout Is Always Bytesसुधार: e.stdout हमेशा Bytes है已修正:e.stdout 总是 bytes

The original printed e.stdout[:100] and showed nice text. But on TimeoutExpired, e.stdout is always bytes, even with text=True (the Python docs say so). The original code really prints a b'...' value — see below — so the fixed code calls .decode(). We also replaced ping google.com with a local command, so the example works without a network and gives the same output every time.

मूल में e.stdout[:100] print करके सुंदर text दिखाया गया था। पर TimeoutExpired पर e.stdout हमेशा bytes होता है, text=True के साथ भी (Python docs यही कहते हैं)। मूल code असल में b'...' value print करता है — नीचे देखें — इसलिए ठीक किया code .decode() बुलाता है। हमने ping google.com को local command से भी बदला, ताकि example बिना network के चले और हर बार वही output दे।

原文打印 e.stdout[:100] 并展示了整齐的文本。但在 TimeoutExpired 中,e.stdout 总是 bytes,即使设置了 text=True(Python 文档就是这么写的)。原代码实际打印的是 b'...' 形式的值 — 见下方 — 所以修正后的代码调用了 .decode()。我们还把 ping google.com 换成了本地命令,这样不需要网络,每次输出也都一样。

OUTPUT OF THE ORIGINAL print LINEमूल print LINE का OUTPUT原 print 行的输出
Timed out after 2.5s Partial output: b'tick 1\ntick 2\ntick 3\n'
⚠️
TimeoutExpired Kills the ChildTimeoutExpired Child को ख़त्म कर देता हैTimeoutExpired 会杀死子进程

When the timeout fires, run() kills the child (SIGKILL on Unix), waits for it, and then raises TimeoutExpired. Whatever was captured before that is on e.stdout and e.stderr (as bytes). Never expect the child to finish nicely after a timeout.

Timeout होने पर run() child को मार देता है (Unix पर SIGKILL), उसका इंतज़ार करता है, और फिर TimeoutExpired raise करता है। उससे पहले जो पकड़ा गया था वह e.stdout और e.stderr में (bytes में) है। Timeout के बाद child के आराम से ख़त्म होने की उम्मीद मत रखिए।

超时触发时,run() 会杀死子进程(Unix 上是 SIGKILL)、等待它退出,然后抛出 TimeoutExpired。在此之前捕获到的内容保存在 e.stdout 和 e.stderr 中(为 bytes)。超时之后,别指望子进程能体面地结束。

🧪 Simulator: run() Lab🧪 Simulator: run() Lab🧪 模拟器:run() 实验室

Pick a command and switch the four options on and off. Watch where stdout and stderr go, what type r.stdout becomes, and what appears on the screen. All 64 combinations were run for real; the time is the real time each one took.

एक command चुनिए और चारों options चालू-बंद कीजिए। देखिए stdout और stderr कहाँ जाते हैं, r.stdout किस type का बनता है, और screen पर क्या आता है। सभी 64 combinations असल में चलाए गए; समय वही असली समय है जो हर एक ने लिया।

选一个命令,打开或关闭四个选项。观察 stdout 和 stderr 流向哪里、r.stdout 变成什么类型,以及屏幕上出现了什么。全部 64 种组合都真实运行过;显示的时间就是每次运行的真实耗时。

run() Labrun() Labrun() 实验室64 real runs64 असली runs64 次真实运行
Command:Command:命令:
Options:Options:选项:
Pythonr = subprocess.run(...)r.stdout →
stdin
stdout
stderr
child processchild process子进程the real programअसली program真正的程序
CodeCode代码
What you see on the screenScreen पर क्या दिखता है屏幕上看到的内容

SectionSection第 06 节

subprocess.run — Key Arguments 🎛️subprocess.run — मुख्य Arguments 🎛️subprocess.run — 关键参数 🎛️

ArgumentArgument参数DefaultDefault默认值Purposeकाम作用Adviceसलाह建议
args—The command, as a list of stringsCommand, strings की list के रूप में命令,以字符串列表表示Always a listहमेशा list始终用列表
capture_outputFalseCapture stdout and stderrstdout और stderr पकड़ें捕获 stdout 和 stderrTrue when you need the output (cannot be combined with stdout=/stderr=)जब output चाहिए तब True (stdout=/stderr= के साथ नहीं चलता)需要输出时设为 True(不能与 stdout=/stderr= 同时使用)
textFalseDecode bytes to str (locale encoding)Bytes को str में बदलें (locale encoding)把 bytes 解码成 str(使用本地编码)Almost always Trueलगभग हमेशा True几乎总是设为 True
checkFalseRaise CalledProcessError on a non-zero exitNon-zero exit पर CalledProcessError raise करें退出码非零时抛出 CalledProcessErrorTrue for automation, False when you only want to lookAutomation के लिए True, सिर्फ़ देखना हो तो False自动化时设为 True,只想查看结果时为 False
timeoutNoneKill the child after N seconds and raise TimeoutExpiredN seconds बाद child को मारें और TimeoutExpired raise करेंN 秒后杀死子进程并抛出 TimeoutExpiredSet it on anything that could hangजो भी अटक सकता है उस पर लगाएँ凡是可能卡住的命令都要设置
inputNoneSend a string/bytes to the child's stdinChild के stdin को string/bytes भेजें向子进程的 stdin 发送字符串/bytesA str needs text=Truestr के लिए text=True चाहिए传 str 时需要 text=True
cwdNoneFolder to run the command inCommand किस folder में चले命令运行的目录Better than os.chdir()os.chdir() से बेहतर比 os.chdir() 更好
envNoneEnvironment variables for the childChild के environment variables子进程的环境变量Copy os.environ first, then change itपहले os.environ copy करें, फिर बदलें先复制 os.environ,再修改
shellFalseRun through a shell (/bin/sh on Unix, cmd.exe on Windows)Shell से चलाएँ (Unix पर /bin/sh, Windows पर cmd.exe)通过 shell 运行(Unix 上是 /bin/sh,Windows 上是 cmd.exe)Keep False (Section 10)False रखें (Section 10)保持 False(第 10 节)
encodingNoneChoose the text encoding yourselfText encoding ख़ुद चुनें自己指定文本编码"utf-8" if the default misbehavesDefault गड़बड़ करे तो "utf-8"默认编码出问题时用 "utf-8"

Passing stdin — sending data to the childstdin देना — child को data भेजना传入 stdin — 向子进程发送数据

import subprocess

# Sort a list of names by piping stdin
names = "charlie\nalice\nbob\ndave"

result = subprocess.run(
    ["sort"],
    input=names,                 # fed to child's stdin
    capture_output=True,
    text=True,
    check=True
)
print(result.stdout)
OUTPUT
alice bob charlie dave

Custom env and cwdअपना env और cwd自定义 env 与 cwd

import subprocess, os

# Run a command in a specific directory with a modified env
custom_env = os.environ.copy()
custom_env["MY_API_KEY"] = "secret-token-123"
custom_env["PYTHONUNBUFFERED"] = "1"

result = subprocess.run(
    ["bash", "-c", "echo $MY_API_KEY; pwd"],
    cwd="/tmp",
    env=custom_env,
    capture_output=True,
    text=True
)
print(result.stdout)
OUTPUT
secret-token-123 /tmp
🔐
Careful With SecretsSecrets के साथ सावधानी小心处理密钥

Passing an API key through env is a good habit — it keeps it out of the command line, where other users on the machine could see it. But this example also prints it, which you would never do in real code. And remember: give the child a copy of os.environ plus your changes, not a dict with only your one variable — otherwise the child loses HOME, locale settings and more.

API key को env से देना अच्छी आदत है — इससे वह command line से बाहर रहती है, जहाँ machine के दूसरे users उसे देख सकते हैं। पर यह example उसे print भी करता है, जो असली code में आप कभी नहीं करेंगे। और याद रखिए: child को os.environ की copy और उसमें आपके बदलाव दीजिए, सिर्फ़ आपके एक variable वाली dict नहीं — वरना child से HOME, locale settings वग़ैरह छिन जाते हैं।

通过 env 传递 API 密钥是个好习惯 — 这样它不会出现在命令行上(同一台机器上的其他用户可能看到命令行)。但这个例子还把它 打印 出来了,真实代码中绝不要这样做。另外记住:要给子进程 os.environ 的 副本 再加上你的修改,而不是只包含你那一个变量的字典 — 否则子进程会丢失 HOME、语言区域设置等。


SectionSection第 07 节

Popen — Low-Level Control for Streaming 📡Popen — Streaming के लिए Low-Level नियंत्रण 📡Popen — 用于流式处理的底层控制 📡

subprocess.run is built on top of subprocess.Popen. Use Popen directly when you need to:

subprocess.run, subprocess.Popen के ऊपर बना है। Popen सीधे तब इस्तेमाल कीजिए जब आपको चाहिए:

subprocess.run 是在 subprocess.Popen 之上构建的。在以下情况下直接使用 Popen:

🔊
Stream Output LiveOutput को Live पढ़ें实时读取输出
line by lineline दर line逐行
run() hands you the output only at the end. With Popen you read stdout while the child is still running — for progress bars, log tailing or long builds.
run() output सिर्फ़ अंत में देता है। Popen से आप child के चलते-चलते stdout पढ़ते हैं — progress bars, log tailing या लंबे builds के लिए।
run() 只在最后才把输出交给你。用 Popen 可以在子进程运行期间读取 stdout — 适合进度条、日志跟踪或长时间的构建。
🔄
Talk Back and Forthआगे-पीछे बात करें来回交互
write + readwrite + read写 + 读
run() sends input once and closes stdin. With Popen you can write a line, read the answer, then write again with p.stdin and p.stdout.
run() input एक बार भेजकर stdin बंद कर देता है। Popen से आप p.stdin और p.stdout के ज़रिए एक line लिख सकते हैं, जवाब पढ़ सकते हैं, फिर से लिख सकते हैं।
run() 只发送一次 input 就关闭 stdin。用 Popen 可以通过 p.stdin 和 p.stdout 写一行、读回答,然后再写。
⏳
Do Other Work Meanwhileइस बीच दूसरा काम同时做别的事
poll(), terminate()poll(), terminate()poll()、terminate()
Start a process in the background, do other work, and check now and then with poll(). Stop it with terminate() or kill().
Process को background में शुरू कीजिए, दूसरा काम कीजिए, और बीच-बीच में poll() से जाँचिए। terminate() या kill() से रोकिए।
在后台启动进程,去做别的工作,时不时用 poll() 检查一下。用 terminate() 或 kill() 停止它。
Figure 2 — The Popen Life Cycleचित्र 2 — Popen का जीवन-चक्र图 2 — Popen 的生命周期
Popen lifecycle1 · SPAWN1 · शुरू1 · 启动Popen([...])returns at onceतुरंत लौटता है立即返回2 · RUNNING2 · चल रहा2 · 运行中p.stdout.readline()read / writeपढ़ें / लिखें读 / 写3 · CHECK3 · जाँच3 · 检查p.poll()None = still runningNone = अभी चल रहाNone = 仍在运行4 · FINISH4 · ख़त्म4 · 结束p.wait() / p.kill()returncode is setreturncode तयreturncode 已设置loop until it has finishedख़त्म होने तक दोहराएँ循环直到结束Popen returns at once — you drive the restPopen तुरंत लौटता है — बाक़ी आप चलाते हैंPopen 立即返回 — 后面由你掌控Unlike run(), the parent keeps working while the child runs.run() के उलट, child के चलते समय parent काम करता रहता है।与 run() 不同,子进程运行时父进程可以继续工作。

Popen does not wait. You start the process, then read, write and poll, and finally wait for it or kill it. That is what makes live progress and interactive tools possible.

Popen इंतज़ार नहीं करता। आप process शुरू करते हैं, फिर पढ़ते, लिखते और poll करते हैं, और आख़िर में उसका इंतज़ार करते हैं या उसे मार देते हैं। इसी से live progress और interactive tools संभव होते हैं।

Popen 不会等待。你启动进程,然后读、写、轮询,最后等待它结束或杀掉它。正是这一点让实时进度和交互式工具成为可能。

Streaming stdout line by linestdout को line दर line पढ़ना逐行读取 stdout

import subprocess

# A task that prints one line per second
p = subprocess.Popen(
    ["bash", "-c",
     "for i in 1 2 3 4 5; do echo 'line '$i; sleep 1; done"],
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,        # merge stderr into stdout
    text=True,
)

# Read one line at a time as it's produced
for line in p.stdout:
    print(f"[live] {line.rstrip()}")

exit_code = p.wait()
print(f"\ndone, exit={exit_code}")
OUTPUT
[live] line 1 [live] line 2 [live] line 3 [live] line 4 [live] line 5 done, exit=0
🔧
Fixed: bufsize=1 Does Not Make Reading Liveसुधार: bufsize=1 पढ़ने को Live नहीं बनाता已修正:bufsize=1 并不能让读取变成实时

The original said "set bufsize=1 for line-buffered reads" and used it in the code. We tested it: lines arrive at exactly the same moments with or without bufsize=1. That setting controls buffering of the pipe on Python's side, mainly for writing. What really decides whether you see lines live is whether the child flushes its output. So we removed it from the example. Try both settings in the lab below.

मूल में कहा गया "line-buffered reads के लिए bufsize=1 लगाइए" और code में यही था। हमने जाँचा: bufsize=1 हो या न हो, lines बिल्कुल एक ही समय पर पहुँचती हैं। यह setting Python की ओर pipe की buffering तय करती है, मुख्यतः लिखने के लिए। Lines live दिखेंगी या नहीं, यह असल में इस पर निर्भर है कि child अपना output flush करता है या नहीं। इसलिए हमने इसे example से हटा दिया। नीचे के lab में दोनों settings आज़माइए।

原文说"为逐行读取设置 bufsize=1",代码里也这么用了。我们实测了:无论有没有 bufsize=1,每一行到达的时间完全相同。这个设置控制的是 Python 这一侧 管道的缓冲,主要影响写入。真正决定你能否实时看到各行的,是 子进程 是否刷新了它的输出。所以我们把它从例子里去掉了。可以在下面的实验室中试试两种设置。

📡 Simulator: Streaming Lab📡 Simulator: Streaming Lab📡 模拟器:流式输出实验室

The child prints one line every 0.5 seconds. Pick what kind of child it is and the bufsize, then press Play. The top track shows when the child printed; the bottom track shows when Python received each line. All times were measured in real runs (rounded to 0.5 s).

Child हर 0.5 second में एक line print करता है। Child का प्रकार और bufsize चुनिए, फिर Play दबाइए। ऊपर वाला track दिखाता है कि child ने कब print किया; नीचे वाला दिखाता है कि Python को हर line कब मिली। सारे समय असली runs में नापे गए (0.5 s तक गोल किए)।

子进程每 0.5 秒打印一行。选择子进程的类型和 bufsize,然后点击播放。上面的轨道显示子进程何时 打印;下面的轨道显示 Python 何时 收到 每一行。所有时间都来自真实运行的测量(四舍五入到 0.5 秒)。

Streaming LabStreaming Lab流式输出实验室timings from real runsअसली runs की timings真实运行的计时
Child:Child:子进程:
bufsize =
0 s0.51.01.52.02.5 s
What Python printedPython ने क्या print कियाPython 打印的内容
⚡
The Real Buffering GotchaBuffering की असली उलझन真正的缓冲陷阱

When its output goes to a pipe instead of a screen, a program like python3 collects output in a block and sends it only when the block is full or the program ends. To make the child send each line at once, run it with python3 -u, add PYTHONUNBUFFERED=1 to a copy of os.environ, or (for other programs) use stdbuf -oL.

जब output screen की जगह pipe में जाता है, तो python3 जैसा program output को एक block में जमा करता है और block भरने या program ख़त्म होने पर ही भेजता है। Child से हर line तुरंत भिजवाने के लिए उसे python3 -u से चलाइए, os.environ की copy में PYTHONUNBUFFERED=1 जोड़िए, या (दूसरे programs के लिए) stdbuf -oL लीजिए।

当输出流向管道而不是屏幕时,像 python3 这样的程序会把输出攒成一块,等块满了或程序结束时才发送。要让子进程每行立刻发出,可以用 python3 -u 运行它,在 os.environ 的 副本 中加入 PYTHONUNBUFFERED=1,或者(对其他程序)使用 stdbuf -oL。


SectionSection第 08 节

Pipes — Chaining Commands Like the Shell 🔗Pipes — Shell की तरह Commands जोड़ना 🔗管道 — 像 shell 一样串联命令 🔗

The shell command ps aux | grep python | wc -l chains three programs: the output of ps feeds grep, and the output of grep feeds wc. In Python you build the same chain by connecting one process's stdout to the next one's stdin.

Shell command ps aux | grep python | wc -l तीन programs जोड़ता है: ps का output grep में जाता है, और grep का output wc में। Python में आप एक process का stdout अगले के stdin से जोड़कर यही chain बनाते हैं।

shell 命令 ps aux | grep python | wc -l 串联了三个程序:ps 的输出喂给 grep,grep 的输出再喂给 wc。在 Python 中,把一个进程的 stdout 接到下一个进程的 stdin,就能搭出同样的链。

Figure 3 — Piping Three Commands Togetherचित्र 3 — तीन Commands को Pipe से जोड़ना图 3 — 用管道串联三个命令
Piping three commands togethershell:shell:shell:ps aux | grep python | wc -lps auxlists every processहर process की सूची列出所有进程|grep pythonkeeps matching linesमेल खाती lines रखता है保留匹配的行|wc -lcounts the lineslines गिनता है统计行数Final result: one number (how many lines matched)अंतिम नतीजा: एक संख्या (कितनी lines मिलीं)最终结果:一个数字(匹配的行数)

Each pipe (|) connects one process's stdout straight to the next one's stdin. All three run at the same time — no temporary file needed.

हर pipe (|) एक process का stdout सीधे अगले के stdin से जोड़ता है। तीनों एक साथ चलते हैं — किसी temporary file की ज़रूरत नहीं।

每个管道(|)都把一个进程的 stdout 直接接到下一个进程的 stdin。三个进程同时运行 — 不需要临时文件。

Building the pipeline in PythonPython में pipeline बनाना在 Python 中搭建管道

import subprocess

# Equivalent of:  ps aux | grep python | wc -l
p1 = subprocess.Popen(
    ["ps", "aux"],
    stdout=subprocess.PIPE
)
p2 = subprocess.Popen(
    ["grep", "python"],
    stdin=p1.stdout,                 # feed p1's output
    stdout=subprocess.PIPE
)
p3 = subprocess.Popen(
    ["wc", "-l"],
    stdin=p2.stdout,                 # feed p2's output
    stdout=subprocess.PIPE,
    text=True
)

# Close upstream stdouts in the parent so SIGPIPE works
p1.stdout.close()
p2.stdout.close()

count = p3.communicate()[0].strip()
print(f"python processes: {count}")
OUTPUT
python processes: 5
🔧
Fixed: Where the Number Comes Fromसुधार: यह संख्या कहाँ से आती है已修正:这个数字从何而来

The original showed 7 (and the figure said "~200 lines" and "5–10 lines"). Those were invented. Our real run printed the value above, and yours will differ, because it counts whatever is running on your machine. Two surprises are hidden in that count: your own script is a python process, and the grep python process itself often shows up in the ps list, because all three programs run at the same time. The Pipeline Flow below shows this with a fixed sample list.

मूल में 7 दिखाया गया था (और चित्र में "~200 lines" और "5–10 lines" लिखा था)। ये बनावटी थे। हमारे असली run ने ऊपर वाली value print की, और आपकी अलग होगी, क्योंकि यह आपकी machine पर चल रही चीज़ें गिनता है। इस गिनती में दो हैरानियाँ छिपी हैं: आपकी अपनी script भी एक python process है, और grep python process ख़ुद अक्सर ps की सूची में आ जाता है, क्योंकि तीनों programs एक साथ चलते हैं। नीचे का Pipeline Flow इसे एक तय sample सूची से दिखाता है।

原文显示的是 7(图中还写着"约 200 行"和"5–10 行"),这些都是编造的。我们的真实运行打印的是上面的值,你的结果也会不同,因为它统计的是 你的 机器上正在运行的东西。这个数字里藏着两个意外:你 自己的脚本 就是一个 python 进程;而且因为三个程序同时运行,grep python 进程本身 也经常出现在 ps 的列表里。下面的管道流程用一份固定的示例列表演示了这一点。

⚠️
Close the Upstream stdoutsUpstream stdouts बंद कीजिए关闭上游的 stdout

After chaining, close the parent's copies of the in-between pipes (p1.stdout.close()). Otherwise, if p2 or p3 stops early, p1 never gets the SIGPIPE signal and can block for ever, waiting for someone to read its output.

Chain बनाने के बाद बीच वाली pipes की parent वाली copies बंद कीजिए (p1.stdout.close())। वरना अगर p2 या p3 जल्दी रुक जाए, तो p1 को SIGPIPE signal कभी नहीं मिलता और वह हमेशा के लिए अटक सकता है, इस इंतज़ार में कि कोई उसका output पढ़े।

串联之后,要在父进程中关闭中间管道的副本(p1.stdout.close())。否则如果 p2 或 p3 提前退出,p1 永远收不到 SIGPIPE 信号,可能一直阻塞,等着有人读取它的输出。

🔗 Simulator: Pipeline Flow🔗 Simulator: Pipeline Flow🔗 模拟器:管道流程

Here ps aux is replaced by a fixed sample list, so everyone gets the same result. The last line is the grep process itself — just as it would appear in a real ps list. Pick a word and watch each line flow through grep into wc -l. The counts were produced by a real Popen pipeline.

यहाँ ps aux की जगह एक तय sample सूची है, ताकि सबको एक ही नतीजा मिले। आख़िरी line ख़ुद grep process है — जैसे असली ps सूची में दिखता। एक शब्द चुनिए और हर line को grep से होकर wc -l तक जाते देखिए। गिनतियाँ असली Popen pipeline से बनी हैं।

这里用一份固定的示例列表代替 ps aux,让每个人得到相同的结果。最后一行就是 grep 进程本身 — 就像在真实的 ps 列表中那样。选一个词,看每一行如何流经 grep 进入 wc -l。计数是由真实的 Popen 管道得出的。

Pipeline FlowPipeline Flow管道流程counts from a real Popen pipelineअसली Popen pipeline की गिनती计数来自真实的 Popen 管道
grep for:grep करें:grep 关键词:
ps auxlines outlines निकलीं行输出
greplines keptlines रखीं行保留
wc -lfinal countअंतिम गिनती最终计数

The shortcut — two run() callsछोटा रास्ता — दो run() calls捷径 — 两次 run() 调用

import subprocess

# Disk usage of the root filesystem: df -h | grep " /$"
result = subprocess.run(
    ["grep", " /$"],
    input=subprocess.run(["df", "-h"], capture_output=True, text=True).stdout,
    capture_output=True,
    text=True
)
print(result.stdout)
OUTPUT
/dev/vda 252G 17G 27G 38% /
📏
Shortcut vs Real Pipeछोटा रास्ता बनाम असली Pipe捷径 vs 真正的管道

This version is simpler, but it is not a real pipe: the first command must finish and its whole output sits in memory before the second one starts. That is fine for small outputs like df -h. For large or never-ending output, use the Popen chain above. (The numbers are from our test machine; yours will differ.)

यह version आसान है, पर असली pipe नहीं: पहले command को ख़त्म होना पड़ता है और उसका पूरा output memory में रहता है, तब दूसरा शुरू होता है। df -h जैसे छोटे output के लिए ठीक है। बड़े या कभी न ख़त्म होने वाले output के लिए ऊपर वाली Popen chain लीजिए। (Numbers हमारी test machine के हैं; आपके अलग होंगे।)

这个版本更简单,但它不是真正的管道:第一个命令必须先 结束,它的全部输出都放在内存里,然后第二个命令才开始。对于 df -h 这样的小输出没问题。输出很大或永不结束时,请用上面的 Popen 链。(数字来自我们的测试机器,你的会不同。)


SectionSection第 09 节

communicate() — Send and Receive Safely 📨communicate() — सुरक्षित भेजना और पाना 📨communicate() — 安全地发送与接收 📨

When you start a process with Popen and need to send input and then read all the output, use communicate(). It writes the input, closes stdin, reads all of stdout and stderr at the same time, and waits for the child to exit — without deadlocks.

जब आप Popen से process शुरू करें और input भेजकर फिर पूरा output पढ़ना हो, तो communicate() लीजिए। यह input लिखता है, stdin बंद करता है, stdout और stderr दोनों एक साथ पूरे पढ़ता है, और child के exit होने का इंतज़ार करता है — बिना deadlock के।

当你用 Popen 启动进程、需要先发送输入再读取全部输出时,使用 communicate()。它会写入输入、关闭 stdin、同时读取全部 stdout 和 stderr,并等待子进程退出 — 不会死锁。

import subprocess

p = subprocess.Popen(
    ["python3"],                 # Python reading a script from stdin
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)

script = """
import math
print('sqrt(2) =', math.sqrt(2))
print('pi     =', math.pi)
"""

stdout, stderr = p.communicate(input=script, timeout=5)
print("--- output ---")
print(stdout)
print(f"exit: {p.returncode}")
OUTPUT
--- output --- sqrt(2) = 1.4142135623730951 pi = 3.141592653589793 exit: 0
📈
Why Not Just Write and Read Yourself?ख़ुद लिखना-पढ़ना क्यों नहीं?为什么不自己写、自己读?

A pipe can hold only a limited amount of data — 64 KB by default on Linux. If the child fills its stdout pipe while you are not reading (say, you called p.wait() first), the child blocks on writing and you block waiting for it to finish. Neither can move: a deadlock. communicate() reads and writes at the same time (with select on Unix, threads on Windows), so the pipe never fills up.

Pipe सीमित data ही रख सकती है — Linux पर default में 64 KB। अगर आप पढ़ नहीं रहे (मान लीजिए आपने पहले p.wait() बुलाया) और child अपनी stdout pipe भर दे, तो child लिखने पर अटक जाता है और आप उसके ख़त्म होने के इंतज़ार में अटक जाते हैं। कोई आगे नहीं बढ़ सकता: deadlock। communicate() एक साथ पढ़ता और लिखता है (Unix पर select से, Windows पर threads से), इसलिए pipe कभी नहीं भरती।

管道只能容纳有限的数据 — 在 Linux 上默认是 64 KB。如果你没在读取(比如先调用了 p.wait()),而子进程写满了 stdout 管道,子进程就会卡在写入上,你则卡在等它结束上。双方都动不了:这就是 死锁。communicate() 会同时读和写(Unix 上用 select,Windows 上用线程),所以管道永远不会被写满。

🔒 Simulator: Deadlock Lab🔒 Simulator: Deadlock Lab🔒 模拟器:死锁实验室

The child writes a block of text. Choose its size and how the parent waits. On our Linux test machine the pipe holds exactly 64 KB; anything bigger with "wait first" gets stuck, and our 2-second timeout rescues the script. Every result was produced by a real run.

Child text का एक block लिखता है। उसका size और parent के इंतज़ार का तरीक़ा चुनिए। हमारी Linux test machine पर pipe ठीक 64 KB रखती है; "पहले wait" के साथ इससे बड़ा कुछ भी अटक जाता है, और हमारा 2-second timeout script को बचाता है। हर नतीजा असली run से है।

子进程写出一块文本。选择它的大小以及父进程的等待方式。在我们的 Linux 测试机上,管道恰好能容纳 64 KB;用"先 wait"时,任何更大的数据都会卡住,我们设置的 2 秒超时把脚本救了回来。每个结果都来自真实运行。

Deadlock LabDeadlock Lab死锁实验室12 real runs12 असली runs12 次真实运行
Child writes:Child लिखता है:子进程写出:
Parent:Parent:父进程:
The stdout pipe (capacity 64 KB)stdout pipe (क्षमता 64 KB)stdout 管道(容量 64 KB)
ChildChild子进程
ParentParent父进程
Resultनतीजा结果

SectionSection第 10 节

The shell=True Trap — Injection 🚨shell=True का जाल — Injection 🚨shell=True 陷阱 — 注入 🚨

shell=True runs your command through /bin/sh -c "...". It is convenient, but if any part of the string comes from a user, you have a shell injection hole: the shell treats characters like ;, $( ) and * as instructions.

shell=True आपका command /bin/sh -c "..." से चलाता है। यह सुविधाजनक है, पर अगर string का कोई भी हिस्सा user से आता है, तो आपके पास shell injection का छेद है: shell ;, $( ) और * जैसे characters को निर्देश मानता है।

shell=True 会通过 /bin/sh -c "..." 运行你的命令。它很方便,但只要字符串中有 任何 部分来自用户,你就有了 shell 注入 漏洞:shell 会把 ;、$( )、* 等字符当作指令。

Figure 4 — Why shell=True Is Dangerousचित्र 4 — shell=True ख़तरनाक क्यों है图 4 — 为什么 shell=True 很危险
Why shell=True is dangerous✓ SAFE — list form (shell=False)✓ सुरक्षित — list form (shell=False)✓ 安全 — 列表形式(shell=False)u = "x.txt; echo HACKED"subprocess.run(["cat", u])RESULTनतीजा结果cat gets ONE argument:'x.txt; echo HACKED'no such file — a harmless errorऐसी file नहीं — हानिरहित error没有这个文件 — 无害的错误✗ DANGEROUS — shell=True✗ ख़तरनाक — shell=True✗ 危险 — shell=Trueu = "x.txt; echo HACKED"run(f"cat {u}", shell=True)RESULTनतीजा结果the shell runs TWO commands:cat x.txt ; echo HACKEDthe ; became a command separator; command separator बन गया; 变成了命令分隔符Swap "echo HACKED" for any command an attacker likes."echo HACKED" की जगह attacker कोई भी command रख सकता है।攻击者可以把 "echo HACKED" 换成任何命令。

The list form passes the input as one argument. The string form with shell=True hands it to the shell as code.

List form input को एक argument की तरह देता है। shell=True वाला string form उसे shell को code की तरह देता है।

列表形式把输入作为一个参数传递。带 shell=True 的字符串形式则把它当作代码交给 shell。

import subprocess

user_input = "nothing.txt; echo HACKED - this command was injected"

print("--- shell=True with an f-string ---")
subprocess.run(f"cat {user_input}", shell=True)

print("--- list form ---")
subprocess.run(["cat", user_input])
OUTPUT
--- shell=True with an f-string --- cat: nothing.txt: No such file or directory HACKED - this command was injected --- list form --- cat: 'nothing.txt; echo HACKED - this command was injected': No such file or directory
🔧
Fixed: The rm -rf / Example and the CVE Claimसुधार: rm -rf / Example और CVE का दावा已修正:rm -rf / 示例与 CVE 说法

The original figure used ; rm -rf /. We use the harmless echo HACKED instead, so you can run it safely and see the injected command really execute (above). It also claimed shell=True is "Python's biggest CVE source" — there is no source for that ranking. What is true: OS command injection (CWE-78) is one of the most common and most serious kinds of security bug, and shell=True with user input is a classic way to create it.

मूल चित्र में ; rm -rf / था। हमने उसकी जगह हानिरहित echo HACKED लिया, ताकि आप इसे सुरक्षित चला सकें और inject किए command को सच में चलते देख सकें (ऊपर)। यह दावा भी था कि shell=True "Python का सबसे बड़ा CVE स्रोत" है — इस ranking का कोई स्रोत नहीं। सच यह है: OS command injection (CWE-78) सबसे आम और गंभीर security bugs में से एक है, और user input के साथ shell=True इसे पैदा करने का पुराना तरीक़ा है।

原图用的是 ; rm -rf /。我们换成了无害的 echo HACKED,这样你可以安全地运行它,亲眼看到注入的命令真的被执行(见上方)。原文还说 shell=True 是"Python 最大的 CVE 来源"— 这个排名没有出处。真实情况是:操作系统命令注入(CWE-78)是最常见、最严重的安全漏洞之一,而把用户输入配合 shell=True 使用正是造成它的典型方式。

❌ Never do this with user inputUser input के साथ यह कभी न करें绝不要这样处理用户输入
subprocess.run(f"cat {user_file}", shell=True)
subprocess.run("grep " + query + " log.txt", shell=True)
os.system(f"rm {filename}")
✅ Do this insteadइसकी जगह यह करें应该这样做
subprocess.run(["cat", "--", user_file])
subprocess.run(["grep", "--", query, "log.txt"])
os.remove(filename)

One more trap: inputs that look like optionsएक और जाल: options जैसे दिखने वाले inputs另一个陷阱:看起来像选项的输入

import subprocess

user_file = "--version"          # looks like a filename, acts like an option

print("--- list form ---")
r = subprocess.run(["cat", user_file], capture_output=True, text=True)
print(r.stdout.splitlines()[0])

print("--- list form with -- ---")
r = subprocess.run(["cat", "--", user_file], capture_output=True, text=True)
print(r.stderr.strip())
OUTPUT
--- list form --- cat (GNU coreutils) 9.4 --- list form with -- --- cat: --version: No such file or directory
🔧
Fixed: "List Form Is Completely Safe"सुधार: "List Form पूरी तरह सुरक्षित है"已修正:"列表形式完全安全"

The original golden rule said "List form + shell=False is completely safe." It stops shell injection, but not argument injection: if the user's value starts with -, the program reads it as an option. Above, the "filename" --version made cat print its version. With some tools (for example git or tar) a sneaky option can run commands. Put "--" before user values — it tells most programs "no more options after this".

मूल golden rule में कहा गया था "List form + shell=False पूरी तरह सुरक्षित है।" यह shell injection रोकता है, पर argument injection नहीं: अगर user की value - से शुरू हो, तो program उसे option समझता है। ऊपर "filename" --version ने cat से उसका version print करवा दिया। कुछ tools (जैसे git या tar) में चालाक option commands चला सकता है। User values से पहले "--" रखिए — यह ज़्यादातर programs को बताता है "इसके बाद कोई option नहीं"।

原黄金法则说"列表形式 + shell=False 完全安全"。它能阻止 shell 注入,却挡不住 参数注入:如果用户的值以 - 开头,程序会把它当作选项。上面这个"文件名" --version 就让 cat 打印了版本号。对某些工具(如 git 或 tar),一个狡猾的选项甚至能执行命令。在用户提供的值之前加上 "--" — 它告诉大多数程序"后面不再有选项"。

🚨 Simulator: Injection Lab🚨 Simulator: Injection Lab🚨 模拟器:注入实验室

Pick a "user input" and a way to call cat. See exactly which arguments cat receives and what really happens. The arguments were printed by a real helper program, and every output comes from a real run in a folder that holds report.txt and my file.txt.

एक "user input" और cat बुलाने का तरीक़ा चुनिए। देखिए cat को ठीक कौन-से arguments मिलते हैं और असल में क्या होता है। Arguments एक असली helper program ने print किए, और हर output report.txt और my file.txt वाले folder में असली run से है।

选一个"用户输入"和一种调用 cat 的方式。看看 cat 究竟收到了哪些参数,以及实际发生了什么。参数是由一个真实的辅助程序打印的,每个输出都来自在包含 report.txt 和 my file.txt 的文件夹中的真实运行。

Injection LabInjection Lab注入实验室18 real runs18 असली runs18 次真实运行
User input:User input:用户输入:
Call:Call:调用方式:
What runs:क्या चलता है:实际运行:
Arguments cat receivescat को मिलने वाले argumentscat 收到的参数
OutputOutput输出
🔒
When Is shell=True Acceptable?shell=True कब ठीक है?什么时候可以用 shell=True?

Only when the command is a fixed string with no user input in it, and you really need shell features (pipes, wildcards, redirects). Even then, prefer a Popen chain or list form. If you truly must put a value into a shell string, wrap it with shlex.quote().

सिर्फ़ तब जब command बिना user input वाली तय string हो, और आपको सच में shell features (pipes, wildcards, redirects) चाहिए। तब भी Popen chain या list form बेहतर है। अगर सच में किसी value को shell string में रखना ही पड़े, तो उसे shlex.quote() में लपेटिए।

只有当命令是 不含任何用户输入的固定字符串,并且你确实需要 shell 功能(管道、通配符、重定向)时才可以。即使这样,也优先用 Popen 链或列表形式。如果实在必须把某个值放进 shell 字符串,请用 shlex.quote() 包起来。


SectionSection第 11 节

Real-World Example — Git Status Dashboard 🗂️असली Example — Git Status Dashboard 🗂️真实案例 — Git 状态面板 🗂️

A small tool that checks several git repositories and reports which ones have uncommitted changes. It uses run, check=True, a timeout, cwd and error handling — the pieces you need in real scripts. For a real run we created four sample folders: api (3 changed files), web (clean, tracking a remote branch), docs (a plain folder, not a repo) and blog (does not exist).

एक छोटा tool जो कई git repositories जाँचता है और बताता है किनमें uncommitted बदलाव हैं। इसमें run, check=True, timeout, cwd और error handling है — असली scripts के ज़रूरी हिस्से। असली run के लिए हमने चार sample folders बनाए: api (3 बदली files), web (साफ़, remote branch को track करता), docs (सादा folder, repo नहीं) और blog (मौजूद ही नहीं)।

一个检查多个 git 仓库、报告哪些有未提交改动的小工具。它用到了 run、check=True、超时、cwd 和错误处理 — 正是真实脚本需要的部件。为了真实运行,我们创建了四个示例文件夹:api(3 个改动的文件)、web(干净,跟踪一个远程分支)、docs(普通文件夹,不是仓库)和 blog(根本不存在)。

import subprocess
from pathlib import Path

def git_status(repo_path: Path) -> dict:
    """Return a summary of the git status of one repo."""
    try:
        # --porcelain gives machine-readable output
        result = subprocess.run(
            ["git", "status", "--porcelain", "-b"],
            cwd=repo_path,
            capture_output=True,
            text=True,
            timeout=10,
            check=True
        )
    except subprocess.CalledProcessError as e:
        return {"name": repo_path.name, "error": e.stderr.strip()}
    except subprocess.TimeoutExpired:
        return {"name": repo_path.name, "error": "timed out"}
    except FileNotFoundError:
        return {"name": repo_path.name, "error": "git not installed"}

    lines = result.stdout.splitlines()
    branch = lines[0].replace("## ", "") if lines else "?"
    changes = [l for l in lines[1:] if l.strip()]

    return {
        "name":    repo_path.name,
        "branch":  branch,
        "dirty":   len(changes) > 0,
        "changes": len(changes)
    }

# Scan multiple repos
repos = [Path("work/api"), Path("work/web"),
         Path("work/docs"), Path("work/blog")]

for repo in repos:
    info = git_status(repo)
    if "error" in info:
        print(f"{info['name']:12s}  ✗ {info['error']}")
    else:
        status = "DIRTY" if info["dirty"] else "clean"
        print(f"{info['name']:12s}  {status:6s}  ({info['changes']} changes)  [{info['branch']}]")
OUTPUT
api DIRTY (3 changes) [main] web clean (0 changes) [feature/auth...origin/feature/auth] docs ✗ fatal: not a git repository (or any of the parent directories): .git blog ✗ git not installed
🔧
Fixed: A Missing Folder Was Reported as "git not installed"सुधार: ग़ायब Folder को "git not installed" बताया गया已修正:缺失的文件夹被报告成"git not installed"

Look at blog above. The folder does not exist, but the tool says "git not installed". Python raises FileNotFoundError both when the program is missing and when the cwd folder is missing, and the code assumes only the first. The fix: check the folder before running git. Also, the original output for docs was shortened — git's real message is fatal: not a git repository (or any of the parent directories): .git.

ऊपर blog देखिए। Folder मौजूद नहीं, पर tool कहता है "git not installed"। Python FileNotFoundError तब भी raise करता है जब program ग़ायब हो और तब भी जब cwd folder ग़ायब हो, और code सिर्फ़ पहली बात मानता है। इलाज: git चलाने से पहले folder जाँचिए। साथ ही docs का मूल output छोटा किया हुआ था — git का असली संदेश fatal: not a git repository (or any of the parent directories): .git है।

看看上面的 blog。这个文件夹并不存在,但工具却说 "git not installed"。无论是 程序 不存在,还是 cwd 文件夹不存在,Python 都会抛出 FileNotFoundError,而代码只考虑了前一种。修正办法:运行 git 之前先检查文件夹。另外,原文中 docs 的输出被缩短了 — git 的真实信息是 fatal: not a git repository (or any of the parent directories): .git。

def git_status(repo_path: Path) -> dict:
    """Return a summary of the git status of one repo."""
    if not repo_path.is_dir():
        return {"name": repo_path.name, "error": "folder not found"}
    try:
        ...                               # the rest is unchanged
OUTPUT AFTER THE FIXसुधार के बाद OUTPUT修正后的输出
api DIRTY (3 changes) [main] web clean (0 changes) [feature/auth...origin/feature/auth] docs ✗ fatal: not a git repository (or any of the parent directories): .git blog ✗ folder not found

SectionSection第 12 节

Real-World Example — Live Log Tailing 📜असली Example — Live Log Tailing 📜真实案例 — 实时跟踪日志 📜

This tool shows the last lines of a log file and then follows new ones live — like tail -f — colouring each line by its level. We ran it on a sample app.log and pressed Ctrl+C after 1.5 seconds:

यह tool log file की आख़िरी lines दिखाता है और फिर नई lines को live follow करता है — tail -f की तरह — हर line को उसके level के रंग में। हमने इसे sample app.log पर चलाया और 1.5 seconds बाद Ctrl+C दबाया:

这个工具先显示日志文件的最后几行,然后像 tail -f 一样实时跟踪新行,并按级别给每行上色。我们在示例 app.log 上运行它,1.5 秒后按下 Ctrl+C:

import subprocess

def colorize(line: str) -> str:
    if "ERROR" in line:
        return f"\033[31m{line}\033[0m"       # red
    if "WARN" in line:
        return f"\033[33m{line}\033[0m"       # yellow
    if "INFO" in line:
        return f"\033[36m{line}\033[0m"       # cyan
    return line

def tail_log(path: str, lines: int = 100):
    p = subprocess.Popen(
        ["tail", "-n", str(lines), "-f", path],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )
    try:
        for line in p.stdout:
            print(colorize(line.rstrip()))
    except KeyboardInterrupt:
        print("\nStopping tail…")
        p.terminate()                        # polite SIGTERM first
        try:
            p.wait(timeout=2)
        except subprocess.TimeoutExpired:
            p.kill()                         # SIGKILL if it ignores us
            p.wait()
        print("tail exit code:", p.returncode)

tail_log("app.log")
OUTPUT
2026-07-16 14:22:03 INFO server started on :8080 2026-07-16 14:22:04 INFO connected to database 2026-07-16 14:22:15 WARN slow query (1.4s): SELECT * FROM orders 2026-07-16 14:22:17 ERROR failed to reach payment gateway: timeout 2026-07-16 14:22:18 INFO retrying payment gateway (attempt 2/5) Stopping tail… tail exit code: -2
🔧
Fixed: A Real Run, and What Ctrl+C Really Doesसुधार: असली Run, और Ctrl+C असल में क्या करता है已修正:真实运行,以及 Ctrl+C 真正做了什么

The original output was typed by hand. Ours is real (the colours are the real ANSI codes). It also shows a surprise: tail exit code: -2. Ctrl+C sends SIGINT (signal 2) to every process in the terminal's group — so tail was already stopped by Ctrl+C before terminate() ran. The terminate → wait → kill pattern still matters when only your script is interrupted, or when you stop a child for other reasons. We also removed bufsize=1 (Section 07).

मूल output हाथ से लिखा गया था। हमारा असली है (रंग असली ANSI codes से हैं)। इसमें एक हैरानी भी दिखती है: tail exit code: -2। Ctrl+C terminal के group के हर process को SIGINT (signal 2) भेजता है — इसलिए terminate() चलने से पहले ही tail Ctrl+C से रुक चुका था। Terminate → wait → kill pattern तब भी ज़रूरी है जब सिर्फ़ आपकी script रुके, या आप किसी और कारण से child रोकें। हमने bufsize=1 भी हटाया (Section 07)।

原输出是手工编写的。我们的是真实输出(颜色就是真实的 ANSI 代码)。它还揭示了一个意外:tail exit code: -2。Ctrl+C 会向终端进程组中的 每个 进程发送 SIGINT(信号 2)— 所以在 terminate() 运行之前,tail 已经被 Ctrl+C 停掉了。当只有你的脚本被中断,或你出于其他原因要停止子进程时,terminate → wait → kill 的模式依然重要。我们还去掉了 bufsize=1(第 07 节)。

🔑
Graceful Shutdown Patternआराम से बंद करने का Pattern优雅关闭的写法

First terminate() (SIGTERM — "please stop"), wait a couple of seconds, then kill() (SIGKILL — "stop now") only if the child ignored you. Do not kill() straight away: SIGKILL cannot be caught, so the child gets no chance to flush buffers, release locks or delete temp files.

पहले terminate() (SIGTERM — "कृपया रुकिए"), कुछ seconds इंतज़ार, फिर kill() (SIGKILL — "अभी रुको") सिर्फ़ तब जब child ने अनसुना किया। सीधे kill() मत कीजिए: SIGKILL पकड़ा नहीं जा सकता, इसलिए child को buffers flush करने, locks छोड़ने या temp files मिटाने का मौक़ा नहीं मिलता।

先 terminate()(SIGTERM —"请停下"),等几秒,只有当子进程不理会时再 kill()(SIGKILL —"立即停止")。不要一上来就 kill():SIGKILL 无法被捕获,子进程没有机会刷新缓冲区、释放锁或删除临时文件。

🛑 Simulator: Shutdown Lab🛑 Simulator: Shutdown Lab🛑 模拟器:关闭实验室

Pick a child — one that cleans up on SIGTERM, one with no handler, and one that ignores SIGTERM — and a way to stop it. Watch the signals, the waiting and the final returncode. All six combinations were run for real.

एक child चुनिए — एक जो SIGTERM पर सफ़ाई करता है, एक जिसमें कोई handler नहीं, और एक जो SIGTERM अनसुना करता है — और उसे रोकने का तरीक़ा। Signals, इंतज़ार और अंतिम returncode देखिए। सभी छह combinations असल में चलाए गए।

选一个子进程 — 收到 SIGTERM 会清理的、没有处理函数的、忽略 SIGTERM 的 — 再选一种停止方式。观察信号、等待过程和最终的 returncode。全部六种组合都真实运行过。

Shutdown LabShutdown Lab关闭实验室6 real runs6 असली runs6 次真实运行
Child:Child:子进程:
Parent:Parent:父进程:
Child is:Child:子进程状态:
Child codeChild code子进程代码
Parent codeParent code父进程代码
What happenedक्या हुआ发生了什么

SectionSection第 13 节

run vs Popen — Which to Use When ⚖️run बनाम Popen — कब कौन-सा ⚖️run vs Popen — 何时用哪个 ⚖️

Propertyगुण特性subprocess.runsubprocess.Popen
Waits for the child to finish?Child के ख़त्म होने का इंतज़ार?等待子进程结束?Yesहाँ是No — returns at onceनहीं — तुरंत लौटता है否 — 立即返回
Ease of useआसानी易用性Very simple — one callबहुत आसान — एक call非常简单 — 一次调用More setup, more controlज़्यादा setup, ज़्यादा नियंत्रण准备更多,控制更多
Process output line by lineOutput को line दर line process करना逐行处理输出No — you get it after the end (without capture it goes straight to the screen)नहीं — अंत के बाद मिलता है (capture के बिना सीधे screen पर जाता है)不能 — 结束后才拿到(不捕获时会直接显示在屏幕上)Yes — read it as it arrivesहाँ — आते-आते पढ़िए可以 — 边到达边读取
Back-and-forth conversationआगे-पीछे बातचीत来回对话No — input= is sent onceनहीं — input= एक बार जाता है不能 — input= 只发送一次Yes — p.stdin.write() + p.stdout.readline()हाँ — p.stdin.write() + p.stdout.readline()可以 — p.stdin.write() + p.stdout.readline()
Chain into pipesPipes में जोड़ना串成管道Awkwardअसुविधाजनक别扭Natural — stdin=prev.stdoutस्वाभाविक — stdin=prev.stdout自然 — stdin=prev.stdout
Stop it from the parentParent से रोकना从父进程停止它Only via timeoutसिर्फ़ timeout से只能通过 timeoutterminate() / kill() / send_signal()
Best fitसबसे सही最适合Most everyday scriptsज़्यादातर रोज़ की scripts大多数日常脚本Long-running, streaming, interactiveलंबे, streaming, interactive काम长时间运行、流式、交互式任务
🔧
Fixed: communicate() Is Not Interactiveसुधार: communicate() Interactive नहीं है已修正:communicate() 不是交互式的

The original table said Popen gives "full duplex via communicate()". But communicate() is one shot: it sends all the input, closes stdin, reads everything and waits for the end — just like run(input=...). A real back-and-forth conversation uses p.stdin.write(), p.stdin.flush() and p.stdout.readline() step by step (and needs care to avoid the deadlock from Section 09).

मूल table में कहा गया Popen "communicate() से full duplex" देता है। पर communicate() एक बार का काम है: यह सारा input भेजता है, stdin बंद करता है, सब कुछ पढ़ता है और अंत का इंतज़ार करता है — बिल्कुल run(input=...) की तरह। असली आगे-पीछे बातचीत p.stdin.write(), p.stdin.flush() और p.stdout.readline() से क़दम-दर-क़दम होती है (और Section 09 वाले deadlock से बचने के लिए सावधानी चाहिए)।

原表格说 Popen 可以"通过 communicate() 实现全双工"。但 communicate() 是一次性的:它发送全部输入、关闭 stdin、读完所有内容并等待结束 — 和 run(input=...) 一样。真正的来回对话需要一步步使用 p.stdin.write()、p.stdin.flush() 和 p.stdout.readline()(并且要小心避免第 09 节中的死锁)。

📈
The Practitioner's Ruleव्यावहारिक नियम实践者法则

Start with subprocess.run(...). Only when you need live output, a back-and-forth conversation, background work or chained pipes should you reach for Popen. Most scripts never need to touch Popen directly.

शुरुआत subprocess.run(...) से कीजिए। सिर्फ़ तब Popen लीजिए जब आपको live output, आगे-पीछे बातचीत, background काम या जुड़ी हुई pipes चाहिए। ज़्यादातर scripts को Popen सीधे छूने की ज़रूरत ही नहीं पड़ती।

从 subprocess.run(...) 开始。只有在需要实时输出、来回对话、后台任务或串联管道时,才去用 Popen。大多数脚本根本不需要直接使用 Popen。


SectionSection第 14 节

Common Pitfalls (and Good Habits) 🩹आम ग़लतियाँ (और अच्छी आदतें) 🩹常见陷阱(与好习惯)🩹

❌
shell=True With User InputUser Input के साथ shell=Trueshell=True 搭配用户输入
use the list formlist form लें用列表形式
A textbook shell injection hole. An attacker adds ; any-command to the input and the shell runs it.
Shell injection का किताबी छेद। Attacker input में ; any-command जोड़ता है और shell उसे चला देता है।
教科书级的 shell 注入漏洞。攻击者在输入里加上 ; 任意命令,shell 就会执行它。
❌
wait() Before Reading a PipePipe पढ़ने से पहले wait()读管道之前先 wait()
use communicate()communicate() लें用 communicate()
A deadlock waiting to happen. The child fills the 64 KB pipe and blocks; you block waiting for it. Use communicate(), or read while it runs.
होने वाला deadlock। Child 64 KB pipe भरकर अटक जाता है; आप उसके इंतज़ार में अटक जाते हैं। communicate() लें, या चलते-चलते पढ़ें।
随时会发生的死锁。子进程写满 64 KB 的管道后阻塞,你则阻塞在等待它上。请用 communicate(),或在运行过程中读取。
❌
No Timeout on Network CommandsNetwork Commands पर Timeout नहीं网络命令不设超时
timeout= is not optionaltimeout= ज़रूरी हैtimeout= 不是可选项
ping, curl, ssh, git clone and package installs can all hang for ever. Always set a timeout.
ping, curl, ssh, git clone और package installs हमेशा के लिए अटक सकते हैं। हमेशा timeout लगाइए।
ping、curl、ssh、git clone 和软件包安装都可能永远卡住。一定要设置超时。
✅
Use text=True (or encoding=)text=True (या encoding=) लें使用 text=True(或 encoding=)
text=True almost everywhereलगभग हर जगह text=True几乎处处用 text=True
Without it, stdout and stderr are bytes, and comparisons like out == "yes\n" silently fail. Just turn on text mode.
इसके बिना stdout और stderr bytes होते हैं, और out == "yes\n" जैसी तुलनाएँ चुपचाप fail होती हैं। बस text mode चालू कीजिए।
不加它,stdout 和 stderr 都是 bytes,像 out == "yes\n" 这样的比较会悄悄失败。直接打开文本模式吧。
✅
Prefer check=True in ScriptsScripts में check=True脚本中优先 check=True
fail fastfail fast快速失败
Failing fast is safer than carrying on silently. A non-zero exit raises CalledProcessError with all the details.
चुपचाप चलते रहने से जल्दी fail होना सुरक्षित है। Non-zero exit सारे details के साथ CalledProcessError raise करता है।
快速失败比悄悄继续更安全。非零退出码会抛出带有全部细节的 CalledProcessError。
✅
Unbuffer a Python ChildPython Child को Unbuffer करें让 Python 子进程不缓冲
flush earlierजल्दी flush尽早刷新
A python3 child buffers its output when it writes to a pipe. Use python3 -u, or env={**os.environ, "PYTHONUNBUFFERED": "1"} — always merged with os.environ.
python3 child pipe में लिखते समय अपना output buffer करता है। python3 -u लें, या env={**os.environ, "PYTHONUNBUFFERED": "1"} — हमेशा os.environ के साथ मिलाकर।
python3 子进程向管道写入时会缓冲输出。请用 python3 -u,或者 env={**os.environ, "PYTHONUNBUFFERED": "1"} — 一定要与 os.environ 合并。
🔧
Fixed: The PYTHONUNBUFFERED Snippetसुधार: PYTHONUNBUFFERED वाला Snippet已修正:PYTHONUNBUFFERED 的代码片段

The original card wrote env{"PYTHONUNBUFFERED": "1"}, which is not valid Python, and a dict with only that key would also wipe out every other environment variable. Use env={**os.environ, "PYTHONUNBUFFERED": "1"}.

मूल card में env{"PYTHONUNBUFFERED": "1"} लिखा था, जो valid Python नहीं है, और सिर्फ़ उस key वाली dict बाक़ी सारे environment variables भी मिटा देती। env={**os.environ, "PYTHONUNBUFFERED": "1"} लिखिए।

原卡片写的是 env{"PYTHONUNBUFFERED": "1"},这不是合法的 Python;而且只含这一个键的字典还会把其他所有环境变量都抹掉。请写成 env={**os.environ, "PYTHONUNBUFFERED": "1"}。

🩺 Simulator: Bug Doctor🩺 Simulator: Bug Doctor🩺 模拟器:Bug 医生

Six real subprocess mistakes. Buggy code on the left, fixed code on the right, changed lines marked. Every output is from a real run.

Subprocess की छह असली ग़लतियाँ। बाईं ओर buggy code, दाईं ओर ठीक किया हुआ, बदली lines चिह्नित। हर output असली run से है।

六个真实的 subprocess 错误。左边是有 bug 的代码,右边是修正后的代码,改动的行已标出。每个输出都来自真实运行。

Bug DoctorBug DoctorBug 医生6 bugs · real outputs6 bugs · असली outputs6 个 bug · 真实输出
❌ BuggyBuggy有 bug
✅ Fixedठीक किया已修正

SectionSection第 15 节

Golden Rules and Quiz 🏆सुनहरे नियम और Quiz 🏆黄金法则与小测验 🏆

🌲 subprocess — Rules to Remembersubprocess — याद रखने वाले नियमsubprocess — 必须记住的规则
1
Always pass the command as a list of strings: ["git", "commit", "-m", msg] — never f"git commit -m {msg}". The list form skips the shell entirely.
Command हमेशा strings की list के रूप में दीजिए: ["git", "commit", "-m", msg] — कभी f"git commit -m {msg}" नहीं। List form shell को पूरी तरह छोड़ देता है।
始终以 字符串列表 传递命令:["git", "commit", "-m", msg] — 绝不要用 f"git commit -m {msg}"。列表形式完全绕开了 shell。
2
Set text=True unless you really handle binary data. Bytes mode means endless .decode() calls and comparisons that quietly fail. (Exception: TimeoutExpired.stdout is always bytes.)
जब तक सच में binary data न हो, text=True लगाइए। Bytes mode का मतलब है बार-बार .decode() और चुपचाप fail होने वाली तुलनाएँ। (अपवाद: TimeoutExpired.stdout हमेशा bytes है।)
除非确实在处理二进制数据,否则请设置 text=True。bytes 模式意味着没完没了的 .decode() 和悄悄失败的比较。(例外:TimeoutExpired.stdout 永远是 bytes。)
3
For automation scripts, use check=True. A clear CalledProcessError on a non-zero exit is safer than silently ignoring failures.
Automation scripts के लिए check=True लें। Non-zero exit पर साफ़ CalledProcessError, चुपचाप failures अनदेखी करने से सुरक्षित है।
自动化脚本请使用 check=True。非零退出时抛出清晰的 CalledProcessError,比悄悄忽略失败更安全。
4
Always set a timeout= on network calls, outside services and anything you do not fully control. Without one, a stuck child makes your script wait for ever.
Network calls, बाहरी services और जिस पर आपका पूरा नियंत्रण नहीं, उन सब पर हमेशा timeout= लगाइए। इसके बिना अटका child आपकी script को हमेशा इंतज़ार कराता है।
对网络调用、外部服务以及任何你无法完全掌控的东西,都要设置 timeout=。没有它,卡住的子进程会让你的脚本永远等下去。
5
Never use shell=True with anything that could contain user data. The list form stops shell injection; add "--" before user values to stop argument injection too.
जिसमें user data हो सकता है उसके साथ shell=True कभी मत लीजिए। List form shell injection रोकता है; argument injection भी रोकने के लिए user values से पहले "--" जोड़िए।
凡是可能含有用户数据的内容,绝不要配合 shell=True 使用。列表形式能阻止 shell 注入;在用户提供的值前加上 "--",还能阻止参数注入。
6
Use subprocess.run() for everyday work. Reach for Popen only when you need live output, a back-and-forth conversation, chained pipes or background work.
रोज़ के काम के लिए subprocess.run() लें। Popen सिर्फ़ तब लें जब live output, आगे-पीछे बातचीत, जुड़ी pipes या background काम चाहिए।
日常工作用 subprocess.run()。只有在需要实时输出、来回对话、串联管道或后台任务时才用 Popen。
7
In Popen pipelines, close the upstream .stdout handles in the parent after passing them to the next child, and read output with communicate() instead of wait() + read().
Popen pipelines में अगले child को देने के बाद parent में upstream .stdout handles बंद कीजिए, और output wait() + read() की जगह communicate() से पढ़िए।
在 Popen 管道中,把上游的 .stdout 交给下一个子进程后,要在父进程中 关闭它;读取输出时用 communicate(),而不是 wait() + read()。
8
Shut down long-running children gracefully: terminate(), wait a few seconds, and only then kill(). A negative returncode such as -15 or -9 tells you which signal ended it.
लंबे चलने वाले children को आराम से बंद कीजिए: terminate(), कुछ seconds इंतज़ार, और तभी kill()। -15 या -9 जैसा negative returncode बताता है किस signal ने उसे ख़त्म किया।
要 优雅地 关闭长时间运行的子进程:先 terminate(),等几秒,然后才 kill()。像 -15 或 -9 这样的负数 returncode 会告诉你是哪个信号结束了它。

Quick Quiz — 8 Questionsछोटा Quiz — 8 सवाल小测验 — 8 道题

🏆 QuizPick an answer to see why.जवाब चुनें और कारण देखें।选一个答案,看看原因。
🎯
Key Takeawayमुख्य सीख关键要点

subprocess starts a separate process and talks to it through stdin, stdout, stderr and an exit code. Use run() with a list of arguments, capture_output=True, text=True, check=True and a timeout for almost everything. Use Popen for live output and pipelines, read with communicate() to avoid deadlocks, keep shell=True away from user input, and stop children with terminate → wait → kill.

subprocess एक अलग process शुरू करता है और stdin, stdout, stderr और exit code से उससे बात करता है। लगभग हर काम के लिए arguments की list, capture_output=True, text=True, check=True और timeout के साथ run() लें। Live output और pipelines के लिए Popen लें, deadlocks से बचने के लिए communicate() से पढ़ें, shell=True को user input से दूर रखें, और children को terminate → wait → kill से रोकें।

subprocess 会启动一个独立的进程,并通过 stdin、stdout、stderr 和退出码与它通信。几乎所有场景都用 run(),配合参数列表、capture_output=True, text=True, check=True 和 timeout。需要实时输出和管道时用 Popen,用 communicate() 读取以避免死锁,让 shell=True 远离用户输入,并按 terminate → wait → kill 的顺序停止子进程。

🐍 Try Python Yourself

Write, run and visualize Python step by step. No install needed.

You have completed Advance topics. View all sections →