Running shell commands in Jupyter with the ! prefix: capture output, avoid injection, and know the limits
Use the ! prefix to run shell commands from Jupyter, capture output into Python variables, and avoid common pitfalls like injection and environment isolation.
02 Aug 2025, 09:39 UTC

Quick answer
\nPrefix any shell command with ! in a Jupyter cell, and the IPython kernel will pass it to the operating system. You can capture the result into a Python variable—out = !ls—and then inspect out.stdout, out.stderr, and out.returncode. This lets you inspect files, run pipelines, or invoke tools without leaving the notebook, but the command runs with the kernel’s user privileges and inherits its environment, not your activated virtualenv.
How the ! prefix works
\nWhen IPython sees ! at the start of a cell input, it strips the mark and sends the remaining string to the system shell via subprocess. The kernel captures stdout as a string and routes stderr to the cell output area. If you assign the command to a variable, IPython returns a special object with .stdout, .stderr, and .returncode attributes, so you can inspect the result programmatically rather than just printing it.
Worked example: listing recent files
\nout = !ls -t | head -n 5\nAfter running the cell, out.stdout will contain the first five lines sorted by modification time, and out.returncode will be 0 if the pipeline succeeded. If you only want the text, print out.stdout or assign it to a regular Python string with files = out.stdout.splitlines().
Checking the return code
\n- \n
- Assign a command and inspect the attribute:
out = !false; thenassert out.returncode == 1to confirm a non-zero exit status. \n - Without assignment, the cell prints the return code at the bottom of the output area. \n
Limits and common mistakes
\n- \n
- Shell injection: building
!commands from unsanitized user input can execute arbitrary code. Always validate inputs or prefersubprocess.runwith a list when working outside notebooks. \n - Environment isolation: the shell inherits the kernel’s environment variables but not necessarily your shell’s
PATHor activeconda/venv. If a command isn’t found, add its full path or adjust the kernel’s environment. \n - Cross‑platform differences: Windows notebooks default to
cmd.exe, which handles pipes and redirection differently. Use%%bashor%%cmdmagic commands to force a specific shell, or stick to!with syntax compatible both places. \n - Output buffering: large outputs may truncate in the notebook UI. Redirect to a file (
!cmd > /tmp/out.txt) for later inspection. \n
Practical verification
\n- \n
- Open a Jupyter Notebook and create a new cell. \n
- Enter
out = !echo helloand execute; verify thatout.stdoutcontainshello. \n - Test a pipeline:
out = !ls -t | head -n 2; confirmout.stdouthas two lines. \n - Check return code:
out = !false; assertout.returncode == 1. \n - On Windows, prefix the cell with
%%bashand runout = !ls -t | head -n 1to use bash syntax. \n
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.