Sometimes the tool you need already exists as a command: df, git,
systemctl, kubectl. Rather than reimplement it, you run it from
Python and work with what it prints. The subprocess module in the
standard library does exactly that.
The one function you'll reach for is subprocess.run. Pass it the
command as a list, where the first item is the program and the rest
are its arguments:
import subprocess
subprocess.run(["ls", "-l", "/etc"])
This runs ls -l /etc, and its output goes straight to your terminal,
just as if you had typed the command yourself. run waits for the
command to finish before your Python continues.
Each argument is its own item in the list. You write ["ls", "-l"],
not "ls -l". Python hands that list straight to the operating system
as the program and its arguments, so nothing reinterprets your spaces
or quotes.
There is another form, shell=True, that takes a single string and
runs it through the shell:
subprocess.run("ls -l /etc", shell=True)
It looks convenient, but it hands your string to a full shell, which
interprets characters like ;, |, and $. If any part of that
string comes from outside your program (a filename a user typed, a
value from an API), someone can smuggle in extra commands. That's a
command-injection bug. The next scenario looks at it in detail.
The habit to build: use the list form. Reach for shell=True only for
a fixed string you wrote yourself, never with untrusted input.
subprocess.run([...]) runs a command from a list of arguments.shell=True runs a string through the shell and is risky with any
input you don't control.