Skip to content

Shell Detector

shell_detector

Shell detection — find the user's login/interactive shell.

Detection order

  1. $SHELL environment variable (set by login daemons on every modern distro).
  2. /proc/<ppid>/exe — resolve the parent process executable.
  3. /etc/passwd entry for the current user.
  4. Fallback: /bin/sh.

Results are cached after the first call (lru_cache).

ShellInfo dataclass

ShellInfo(path, name, family, version='')

Information about the user's shell.

path instance-attribute

path

Absolute path, e.g. /usr/bin/zsh.

name instance-attribute

name

Executable name without path, e.g. zsh.

family instance-attribute

family

Normalised shell family: bash, zsh, fish, ksh, sh, csh, elvish, nushell, xonsh, or unknown.

version class-attribute instance-attribute

version = ''

Version string if detectable, otherwise empty.

supports_readline_prefill

supports_readline_prefill()

Return True if this shell supports pre-filling the command line.

Source code in src/shell_detector.py
def supports_readline_prefill(self) -> bool:
    """Return True if this shell supports pre-filling the command line."""
    return self.family in ("bash", "zsh", "fish", "ksh")

detect cached

detect()

Detect the user's shell and return a :class:ShellInfo.

Results are cached; call detect.cache_clear() in tests.

Source code in src/shell_detector.py
@lru_cache(maxsize=1)
def detect() -> ShellInfo:
    """Detect the user's shell and return a :class:`ShellInfo`.

    Results are cached; call ``detect.cache_clear()`` in tests.
    """
    # 1. $SHELL env var
    env_shell = os.environ.get("SHELL", "").strip()
    if env_shell and Path(env_shell).exists():
        logger.debug("shell_detector: using $SHELL=%s", env_shell)
        return _from_path(env_shell)

    # 2. Parent process
    parent = _from_parent_proc()
    if parent:
        logger.debug("shell_detector: detected from parent proc: %s", parent)
        return _from_path(parent)

    # 3. System user database
    etc_shell = _from_user_db()
    if etc_shell and Path(etc_shell).exists():
        logger.debug("shell_detector: using system login shell: %s", etc_shell)
        return _from_path(etc_shell)

    # 4. Fallback
    logger.debug("shell_detector: falling back to /bin/sh")
    return _from_path("/bin/sh")