os.system

The old one-liners for running a shell command. os.system (Unix, Windows) cannot capture output and returns a platform-dependent status; os.popen (Unix, Windows) is a thin wrapper around subprocess.Popen with shell=True; os.startfile is Windows only. New code should use subprocess.run.

os functionPython 3 (all versions); startfile arguments, cwd, show_cmd 3.10+
Common call
os.popen('git rev-parse HEAD').read()
Returns
system: status int; popen: text file; startfile: None
Replaces
subprocess.run(cmd, shell=True, capture_output=True, text=True) is the modern form
Watch out
system output goes straight to the console, not to a variable
os.system(command) / os.popen(cmd, modemode — popen(): 'r' to read the command's stdout, 'w' to write to its stdin. Anything else raises ValueError.type: str · default: 'r'='r', bufferingbuffering — popen(): same meaning as for open(); 0 (unbuffered) is rejected with ValueError.type: int · default: -1=-1) / os.startfile(path[, operation][, arguments][, cwd][, show_cmd])
→ int | file object | None

Parameters

NameTypeRequiredDescription
commandstryessystem(): the command line, run by the shell (/bin/sh on Unix, COMSPEC - usually cmd.exe - on Windows).
cmdstryespopen(): the command line, also run through the shell.
modestrno ('r')popen(): 'r' to read the command's stdout, 'w' to write to its stdin. Anything else raises ValueError.
bufferingintno (-1)popen(): same meaning as for open(); 0 (unbuffered) is rejected with ValueError.
pathstr | PathLikeyesstartfile(): the file or folder to open with its associated application (Windows only).
operationstrnostartfile(): a shell verb such as 'open', 'print', 'edit', 'explore' or 'find'.

Return value

int | file object | None — system(): an exit status (wait status on Unix, exit code on Windows). popen(): a text file object connected to the command. startfile(): None, it returns as soon as the application is launched.

Common patterns

Capture a command (modern form)
subprocess.run with a list of arguments: no shell, output captured, exit code checked.
import subprocess
result = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output=True, text=True, check=True)
commit = result.stdout.strip()
Read output the old way
os.popen returns a text file; close() (or leaving the with block) waits for the command.
import os
with os.popen('ls -l') as p:
    listing = p.read()
Exit code from os.system on any OS
On Unix the result is a wait status; waitstatus_to_exitcode decodes it. On Windows it already is the exit code.
import os
status = os.system('make build')
code = status if os.name == 'nt' else os.waitstatus_to_exitcode(status)
Open a file with its default app
startfile is Windows only; macOS has the open command and most Linux desktops xdg-open.
import os, subprocess, sys
if sys.platform == 'win32':
    os.startfile('report.pdf')
elif sys.platform == 'darwin':
    subprocess.run(['open', 'report.pdf'])
else:
    subprocess.run(['xdg-open', 'report.pdf'])

Examples

1. Read a command's output with popen
import os, sys with os.popen(f'"{sys.executable}" -c "print(6*7)"') as p: out = p.read() out
Returns
'42\n'
2. close() returns None on success
import os, sys p = os.popen(f'"{sys.executable}" -c "print(1)"') out = p.read() (out, p.close())
Returns
('1\n', None)
3. close() returns a status on failure
import os, sys p = os.popen(f'"{sys.executable}" -c "raise SystemExit(3)"') p.read() status = p.close() (status is None, bool(status))
Returns
(False, True)
4. Turn that status into the exit code on any OS
import os, sys p = os.popen(f'"{sys.executable}" -c "raise SystemExit(3)"') p.read() status = p.close() status if os.name == 'nt' else os.waitstatus_to_exitcode(status)
Returns
3
5. Output line by line
import os, sys with os.popen(f'"{sys.executable}" -c "print(1); print(2)"') as p: lines = p.read().splitlines() lines
Returns
['1', '2']
6. The replacement: subprocess.run
import subprocess, sys r = subprocess.run([sys.executable, '-c', 'print(6*7)'], capture_output=True, text=True) (r.returncode, r.stdout)
Returns
(0, '42\n')
7. shell=True gives a plain exit code
import subprocess, sys r = subprocess.run(f'"{sys.executable}" -c "raise SystemExit(3)"', shell=True) r.returncode
Returns
3

Pitfalls

1. Testing close() against 0
A successful popen command makes close() return None, not 0, so == 0 is False exactly when everything worked.
== 0
import os, sys
p = os.popen(f'"{sys.executable}" -c "print(1)"')
p.read()
p.close() == 0
False
is None
import os, sys
p = os.popen(f'"{sys.executable}" -c "print(1)"')
p.read()
p.close() is None
True
2. Pasting user input into a shell command
system() and popen() hand the whole string to the shell, so ; && | and quotes in the input become shell syntax. Pass a list to subprocess.run instead - each item reaches the program as one argument, untouched.
string for the shell
name = 'report.txt; echo hacked'
f'cat {name}'
'cat report.txt; echo hacked'
list, no shell
import subprocess, sys
name = 'report.txt; echo hacked'
r = subprocess.run([sys.executable, '-c', 'import sys; print(sys.argv[1:])', name], capture_output=True, text=True)
r.stdout
"['report.txt; echo hacked']\n"

When to use

Use it
  • Quick scripts where a shell one-liner is the point (pipes, globbing) and the input is trusted
  • os.startfile: opening a document or folder the way a double click in Explorer would (Windows)
Reach for something else
  • Capturing output, checking errors, timeouts → subprocess.run(..., capture_output=True, text=True, check=True)
  • Anything containing user input → subprocess.run with a list of arguments, no shell
  • Long-running or interactive children → subprocess.Popen

Notes

CPython impl
os.system calls the C library system() (Modules/posixmodule.c). os.popen is Python code in Lib/os.py: subprocess.Popen(cmd, shell=True, text=True, ...) wrapped in os._wrap_close, whose close() waits for the process.
Availability
system and popen: Unix, Windows (not WASI, Android or iOS). startfile: Windows only, so it does not exist on Linux or macOS.
Return value
os.system on Unix returns the wait status, so exit code 3 comes back as 768 (3 << 8); on Windows it returns the exit code itself, 3. popen().close() follows the same rule and returns None for exit code 0.
Output
os.system does not capture anything - the command writes to the same console as Python. That is also why the examples on this page never call it.
startfile
Returns as soon as the application starts; there is no way to wait for it or read its exit status.

FAQ

os.system and os.popen exist on Unix and Windows (not WASI, Android or iOS), and os.startfile is Windows only. os.system lets the command write straight to the console, bypassing Python, so its output cannot be shown or captured; the examples use os.popen and subprocess with a Python child process instead, which behave the same on every OS.