Is it possible to access system env vars in Qt?

Reading sourced Bash environment variables from your Python Qt application
Heads up! You've already completed this tutorial.

I'm working with a PyQt application that requires the user to source environment variables from a Bash file. I can see these variables in the terminal after sourcing the file, but when I try to access them inside my Qt application using os.getenv(), they return None. How can I access sourced Bash environment variables from within a PyQt or PySide application?

When you open a terminal and run source setup.sh, the variables defined in that script are loaded into that specific shell session's environment. Any child process launched from that same shell session will inherit those variables.

However, your Python/Qt application is a separate process. If it was launched from a different shell session — for example, from an IDE, a desktop shortcut, or a different terminal window — it won't have access to variables sourced in another session. Each process gets its own copy of the environment at the time it starts, and changes in one process don't propagate to others.

There's also a subtle timing issue that can cause confusion. Consider this code:

python
out = subprocess.run([
    "bash", "-c",
    f"source /path/to/setup.sh -r && "
    f"{os.getenv('MY_VAR')}"
], stderr=subprocess.STDOUT, stdout=subprocess.PIPE)

Here, os.getenv('MY_VAR') is evaluated by Python before the subprocess starts. At that point, the variable hasn't been sourced yet, so it resolves to None. The subprocess ends up trying to run bash -c "source /path/to/setup.sh -r && None", which produces the error bash: None: command not found.

The fix is to keep everything inside the Bash command string so that Bash evaluates the variable after sourcing:

python
out = subprocess.run([
    "bash", "-c",
    "source /path/to/setup.sh -r && echo $MY_VAR"
], stderr=subprocess.STDOUT, stdout=subprocess.PIPE, text=True)

print(out.stdout.strip())  # Should print the value of MY_VAR

By using $MY_VAR inside the Bash command string (not an f-string interpolation of os.getenv()), Bash itself resolves the variable after the source command has run.

Approach 1: Read variables via subprocess

If you only need to read a few specific variables, you can source the file in a subprocess and echo the values back to Python:

python
import subprocess


def get_env_var_from_bash(script_path, var_name):
    """Source a Bash script and retrieve a single environment variable."""
    result = subprocess.run(
        ["bash", "-c", f"source {script_path} && echo ${var_name}"],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )
    value = result.stdout.strip()
    return value if value else None


# Usage
ocpi_root = get_env_var_from_bash(
    "/path/to/cdk/opencpi-setup.sh -r",
    "OCPI_ROOT_DIR",
)
print(ocpi_root)

This works well for grabbing one or two values. If you need many variables, though, spawning a subprocess for each one gets inefficient.

Approach 2: Load all sourced variables into your Python process

A more thorough approach is to source the Bash file in a subprocess, dump the entire environment, and then parse it back into Python's os.environ. This makes every sourced variable available throughout your application — including inside your PyQt or PySide widgets.

python
import os
import subprocess


def load_bash_env(script_path):
    """Source a Bash script and load all resulting env vars into this process."""
    command = f"source {script_path} && env"
    result = subprocess.run(
        ["bash", "-c", command],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )

    if result.returncode != 0:
        print(f"Error sourcing script: {result.stderr}")
        return

    for line in result.stdout.splitlines():
        # Environment variables are in KEY=VALUE format.
        # Split on the first '=' only, since values can contain '='.
        if "=" in line:
            key, _, value = line.partition("=")
            os.environ[key] = value


# Source the file early, before creating your Qt application
load_bash_env("/path/to/cdk/opencpi-setup.sh -r")

# Now these work anywhere in your code
print(os.getenv("OCPI_ROOT_DIR"))

Once you've called load_bash_env(), every part of your application — including Qt widgets, signals, slots, and helper modules — can use os.getenv() or os.environ to access those variables normally.

One thing to be aware of: some environment variables may contain newlines in their values (this is rare but possible). The simple line-by-line parsing above handles the vast majority of cases, but if your setup script produces multi-line values, you may need to use the null-byte-delimited approach shown below:

python
import os
import subprocess


def load_bash_env(script_path):
    """Source a Bash script and load all resulting env vars into this process.

    Uses null-byte delimited output to handle values containing newlines.
    """
    command = f"source {script_path} && python3 -c \"import os; print('\\0'.join(k+'='+v for k,v in os.environ.items()))\""
    result = subprocess.run(
        ["bash", "-c", command],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )

    if result.returncode != 0:
        print(f"Error sourcing script: {result.stderr}")
        return

    for entry in result.stdout.split("\0"):
        if "=" in entry:
            key, _, value = entry.partition("=")
            os.environ[key] = value

Approach 3: Use the envbash package

The envbash package wraps up the logic of sourcing a Bash file and importing the environment variables into your Python process. It handles edge cases for you and provides a clean API.

Install it with pip:

bash
pip install envbash

Then use it like this:

python
import os

from envbash import load_envbash

# Source the script and load all variables into os.environ
load_envbash("/path/to/cdk/opencpi-setup.sh -r")

# Now accessible everywhere
print(os.getenv("OCPI_ROOT_DIR"))

This is the most concise option, and it works well if you're comfortable adding an external dependency.

Putting it all together with PyQt6

Here's a complete example showing how to source a Bash environment file and use the resulting variables inside a PyQt6 application. This uses the manual subprocess approach (Approach 2) so there are no extra dependencies.

python
import os
import subprocess
import sys

from PyQt6.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


def load_bash_env(script_path):
    """Source a Bash script and load all resulting env vars into this process."""
    command = f"source {script_path} && env"
    result = subprocess.run(
        ["bash", "-c", command],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )

    if result.returncode != 0:
        print(f"Error sourcing script: {result.stderr}")
        return

    for line in result.stdout.splitlines():
        if "=" in line:
            key, _, value = line.partition("=")
            os.environ[key] = value


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Environment Variable Viewer")

        layout = QVBoxLayout()

        # Read some environment variables and display them
        home = os.getenv("HOME", "Not set")
        path = os.getenv("PATH", "Not set")
        ocpi_root = os.getenv("OCPI_ROOT_DIR", "Not set")

        layout.addWidget(QLabel(f"HOME: {home}"))
        layout.addWidget(QLabel(f"PATH: {path[:80]}..."))
        layout.addWidget(QLabel(f"OCPI_ROOT_DIR: {ocpi_root}"))

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


def main():
    # Load environment from a Bash script before creating the app.
    # Replace this path with your actual script path.
    script_path = os.path.expanduser("~/cdk/opencpi-setup.sh")
    if os.path.exists(script_path):
        load_bash_env(script_path)
    else:
        print(f"Script not found: {script_path} (skipping)")

    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

The pattern is straightforward: call load_bash_env() early in your program (before you create any widgets that need the variables), and then use os.getenv() anywhere in your application just as you normally would.

Which approach should you use?

Approach Best for
Subprocess per variable Quick lookups of one or two values
Load all via subprocess Full access to sourced variables, no extra dependencies
envbash package Clean API, handles edge cases, okay with adding a dependency

All three approaches solve the same underlying problem: getting variables from a Bash environment into your Python process so that your Qt application can see them. Pick whichever fits your project best, and you'll have those variables available wherever you need them.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Create GUI Applications with Python & Qt6 by Martin Fitzpatrick

(PySide6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!

More info Get the book

Martin Fitzpatrick

Is it possible to access system env vars in Qt? was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.