How to Run a Python Script in Linux

How to Run a Python Script in Linux

Reading time1 min
#Python#Linux#CommandLine#Automation#Systemd

How to Run a Python Script in Linux

The short answer is python3 script.py. The rest of this post covers the details that cause trouble in practice: which python you are actually running, executable scripts, arguments, virtual environments, and running scripts unattended.

Check that Python is there

python3 --version

On Ubuntu and Debian the command is python3; plain python gives "command not found" unless you install python-is-python3. If python3 itself is missing, install it with sudo apt install python3 or sudo dnf install python3.

Start Python in the terminal

Run python3 with no arguments to get the interactive interpreter:

$ python3
Python 3.12.3 (main, ...) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 2 ** 10
1024
>>> exit()

Leave it with exit() or Ctrl+D. Since Python 3.13 the interpreter has colored output and multi-line editing built in.

Two related options:

python3 -c 'import sys; print(sys.version)'   # run a one-liner
python3 -i script.py                          # run a script, then stay in the interpreter

-i is handy for poking at the variables a script left behind.

Run a script file

python3 hello.py
python3 /home/deploy/scripts/hello.py

Relative file paths inside the script are resolved against your current directory, not the script's location. A script that opens config.yaml works when you cd into its folder and fails from anywhere else. Build paths from the script's own location instead:

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent
config = (BASE_DIR / "config.yaml").read_text()

Make the script executable

Put a shebang on the first line:

#!/usr/bin/env python3
print("Hello from Linux")

Then set the execute bit and run it by path:

chmod +x hello.py
./hello.py

#!/usr/bin/env python3 uses the first python3 in PATH, so an activated virtual environment is respected. The ./ is required because the current directory is not in PATH. To run the script by name from anywhere, copy it to ~/.local/bin (for your user) or /usr/local/bin (for everyone).

Typical errors:

  • Permission denied: the execute bit is missing (chmod +x), or the file lives on a filesystem mounted noexec, such as /tmp on hardened systems.
  • /usr/bin/env: 'python3\r': No such file or directory: the file has Windows line endings. Fix with sed -i 's/\r$//' hello.py or dos2unix hello.py.
  • import: command not found: there is no shebang, so the shell tried to run Python code as a shell script.

Pass arguments

#!/usr/bin/env python3
import sys

name = sys.argv[1] if len(sys.argv) > 1 else "world"
print(f"Hello, {name}!")
python3 hello.py Alice      # Hello, Alice!

sys.argv[0] is the script name and the arguments follow. For anything with options or flags, use argparse from the standard library: it gives you --help and error messages for free.

Run a module with -m

python3 -m http.server 8000      # serve the current directory over HTTP
python3 -m json.tool data.json   # pretty-print JSON
python3 -m my_package            # runs my_package/__main__.py

-m finds the module on Python's import path and runs it. Use it for packages, and use python3 -m pip instead of a bare pip to make sure pip belongs to the same interpreter.

Use a virtual environment

Scripts with third-party dependencies should run from a virtual environment, not the system Python:

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python app.py
deactivate

You do not have to activate it. Calling the venv's interpreter directly does the same thing, which is what you want in cron jobs, systemd units and scripts:

/opt/my-app/.venv/bin/python /opt/my-app/app.py

If python3 -m venv fails on Ubuntu or Debian, install python3-venv.

For single-file scripts, uv can manage dependencies declared at the top of the file:

# /// script
# dependencies = ["requests"]
# ///
import requests
print(requests.get("https://example.com").status_code)
uv run fetch.py

uv creates and caches a matching environment on first run.

Keep it running after you log out

A script started in an SSH session dies when the session closes. For a quick one-off job:

nohup python3 -u worker.py > worker.log 2>&1 &
tail -f worker.log

-u disables output buffering, so lines show up in the log immediately instead of in large chunks. For interactive work you want to come back to, run the script inside tmux. For anything that should survive reboots, use systemd.

Run on a schedule with cron

Edit your crontab with crontab -e:

*/15 * * * * cd /opt/my-app && /opt/my-app/.venv/bin/python sync.py >> /home/deploy/sync.log 2>&1

cron runs with a minimal environment: a short PATH, no activated venv, none of your shell settings. Use absolute paths for the interpreter, the script and the log, and redirect output, or errors disappear silently.

Run as a systemd service

For long-running scripts such as workers, bots and small APIs, a systemd unit gives you restarts and logging. Create /etc/systemd/system/my-worker.service:

[Unit]
Description=Example Python worker
After=network-online.target
Wants=network-online.target

[Service]
User=deploy
WorkingDirectory=/opt/my-app
ExecStart=/opt/my-app/.venv/bin/python worker.py
Environment=PYTHONUNBUFFERED=1
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now my-worker
journalctl -u my-worker -f

PYTHONUNBUFFERED=1 has the same effect as -u, so print output reaches the journal right away. For scheduled jobs, a systemd timer next to a Type=oneshot service is an alternative to cron with proper logging.

Debugging

python3 -m pdb script.py      # step through from the first line
python3 -X dev script.py      # extra runtime warnings and checks

You can also put breakpoint() anywhere in the code to stop there and get a debugger prompt.

Quick reference

python3                                  # interactive interpreter, exit() to leave
python3 script.py arg1 arg2              # run a script with arguments
python3 -c 'print("hi")'                 # one-liner
python3 -m module_name                   # run a module or package
chmod +x script.py && ./script.py        # needs #!/usr/bin/env python3
python3 -m venv .venv && source .venv/bin/activate
/path/to/.venv/bin/python script.py      # venv without activating (cron, systemd)
nohup python3 -u script.py > out.log 2>&1 &
python3 -m pdb script.py                 # debugger