Skip to content

Instantly share code, notes, and snippets.

@ChrisTollefson
Last active February 15, 2024 17:40
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ChrisTollefson/6294f711922c88ce3aef2e420452eba1 to your computer and use it in GitHub Desktop.
Save ChrisTollefson/6294f711922c88ce3aef2e420452eba1 to your computer and use it in GitHub Desktop.
Bash Startup Files

Bash Startup Files

The Bourne Again Shell (bash) loads and runs various startup files under various conditions. This document attempts to list those files and describe the conditions and order in which they may be loaded.

Summary

Normally, bash loads the following files on startup, depending on whether it is an interactive and/or login shell:

Login shell Non-login shell
Interactive shell
  • /etc/profile
  • ~/.bash_profile, ~/.bash_login, or ~/.profile
  • /etc/bash.bashrc
  • ~/.bashrc
Non-interactive shell
  • /etc/profile
  • ~/.bash_profile, ~/.bash_login, or ~/.profile
  • BASH_ENV file
  • BASH_ENV file

When bash is run as sh:

Login shell Non-login shell
Interactive shell
  • /etc/profile
  • ~/.profile
  • ENV file
  • ENV file
Non-interactive shell
  • /etc/profile
  • ~/.profile
  • none

When bash is run in POSIX mode:

Login shell Non-login shell
Interactive shell
  • ENV file
  • ENV file
Non-interactive shell
  • none
  • none

Contents

Startup files:

Shutdown files:

Variables:

Commands:

Options:

Inputs:

Conditions:

The following startup files might be loaded under various conditions, in approximately the following order.

System profile

  • /etc/profile

The profile loaded for all users when logging in.

Only loaded if:

Sometimes the following are sourced in /etc/profile:

  • Files in /etc/profile.d/*
  • For macOS:
    • /etc/bashrc
      • /etc/bashrc_$TERM_PROGRAM (e.g. /etc/bashrc_Apple_Terminal), sourced in /etc/bashrc

User profile

  • ~/.bash_profile, if exists and not running as sh; otherwise:
    • ~/.bash_login, if exists and not running as sh; otherwise:
      • ~/.profile

The profile loaded for the current user when logging in.

Only loaded if:

Often sources ~/.bashrc, as the login shell is typically also an interactive shell for running commands.

Bash environment

Only loaded if:

System run-commands

  • /etc/bash.bashrc, if compiled in the bash source code (e.g. Debian)

The profile loaded for all users in interactive shells for running commands.

Only loaded if:

NOTE: In the bash source code, the SYS_BASHRC definition in config-top.h controls whether /etc/bash.bashrc will be loaded (if applicable):

/* System-wide .bashrc file for interactive shells. */
/* #define SYS_BASHRC "/etc/bash.bashrc" */

In the current bash source code, the SYS_BASHRC definition is commented out, and this is also true, for example, in the bash source code for macOS as of 10.2 (Jaguar). In contrast, some distros have un-commented this code to enable /etc/bash.bashrc, for example, as patched in the bash source code for Debian as of 3.1 (Sarge).

User run-commands

The profile loaded for the current user in interactive shells for running commands.

Only loaded if:

Often sourced from ~/.bash_profile, so that this profile is also applied to interactive login shells.

POSIX/sh environment

  • The file in ENV, if set

The profile loaded for POSIX mode shells.

Only loaded if:

Shutdown Files

User shutdown file

  • ~/.bash_logout

TODO: document this.

System shutdown file

  • /etc/bash.bash_logout, if compiled in the bash source code (e.g. Debian)

TODO: document this.

NOTE: In the bash source code, the SYS_BASH_LOGOUT definition in config-top.h controls whether /etc/bash.bash_logout will be loaded on shutdown (if applicable):

/* System-wide .bash_logout for login shells. */
/* #define SYS_BASH_LOGOUT "/etc/bash.bash_logout" */

In the current bash source code, the SYS_BASH_LOGOUT definition is commented out, and this is also true, for example, in the bash source code for macOS as of 10.2 (Jaguar). In contrast, some distros have un-commented this code to enable /etc/bash.bash_logout, for example, as patched in the bash source code for Debian as of 3.1 (Sarge).

Variables, Commands, Options and Inputs

The following variables, commands, options and inputs might directly or indirectly affect which startup files are loaded.

If set, specifies the bash environment file for non-interactive bash shells (e.g. those with file input, command input, or non-terminal input). As usual, this file is only loaded if:

If set, specifies the POSIX/sh environment file for interactive sh or POSIX shells. As usual, this file is only loaded if:

Sets the shell to POSIX mode.

Commands

The following commands might affect which startup files are loaded, compared to running bash normally.

When bash is run as sh:

  1. First, for non-POSIX login shells only, loads the system profile (/etc/profile) and user profile (~/.profile only), unless run with --noprofile.
  2. Next, for interactive shells only, loads the POSIX/sh environment file (in ENV, if set).
  3. Finally, the shell enters POSIX mode.

Assuming the user's default shell is bash, the login command runs a new bash login shell.

When bash is run using the exec builtin with the -l option (i.e. exec -l bash or exec -l sh), it runs as a login shell.

Sets the shell to POSIX mode.

--init-file or --rc-file

Loads the specified user run-command file instead of ~/.bashrc. (It is presumed that the last instance of this option in the command will be the one in effect, but this is not documented.) As usual, this file is only loaded if:

--login or -l

The shell behaves as a login shell.

--noprofile

The system profile (/etc/profile) and user profile (~/.bash_profile, or ~/.bash_login, or /.profile) will not be loaded.

--norc

The system run-command (/etc/bash.bashrc) and user run-command (~/.bashrc, or --rcfile, or --init-file) files will not be loaded.

--posix or -o posix

Sets the shell to POSIX mode.

-c

Specifies a command to run. This causes the shell to run non-interactively.

-i

The shell behaves as an interactive shell. Note that this doesn't prevent the shell from exiting after running its commands, if that would have occurred without the -i option.

-s

Causes an argument to the shell to be treated as a positional parameter instead of a file to run. This causes the shell to run interactively instead of running the file non-interactively.

Inputs

No input

A shell run with no arguments and no non-terminal standard input (i.e. no pipes, no redirections, etc.) will run interactively.

File input

A shell run with at least one argument, and without the -c or -s option, will treat the first argument as the name of a file to run non-interactively, then exit.

(Using the -s option causes the first argument to be treated as a positional parameter instead of a file to run, so the shell runs interactively in that case.)

Command input

A command can be specified using the -c option, in which case a new non-interactive shell runs the command, then exits.

Non-terminal input

When a shell is run with input from something other than a terminal (for example, from a pipe or redirection), a new non-interactive shell runs the supplied input, then exits. (In this situation, the -s option can be used to specify positional parameters.)

Conditions

The following conditions might affect which startup files are loaded.

Generally, an interactive shell executes commands supplied from terminal input, then continues running, waiting for additional commands from the terminal, until explicitly exited.

Generally, a non-interactive shell executes commands supplied from a file (e.g. shell script), direct command, or non-terminal input (e.g. pipe or redirection), then exits when complete.

The difference in startup files between interactive and non-interactive shells is as follows.

For interactive shells:

For non-interactive shells:

Running a non-interactive shell

Run a non-interactive shell by supplying commands to be run when invoking the shell. This could be done in one of the following ways. The shell will exit upon completion of the commands.

  • Without the -c or -s options, specify the name of a file to run as the first argument.

    bash "/path/to/some_script"

    Alternatively, add an appropriate shebang (such as #!/usr/bin/env bash) to a shell script and invoke it directly.

    "/path/to/some_script"
  • With the -c option, specify a command string to run.

    bash -c 'printf "flags: %s\n" "${-}"'
  • With no arguments (or with arguments using the -s option), send text commands to the shell's standard input using pipes, redirections, or some other non-terminal input.

    echo 'printf "flags: %s\n" "${-}"' | bash
    echo 'printf "flags: %s\n" "${-}"' | bash -s "args"

Running an interactive shell

  • Run the shell with no input.

    bash
  • Run the shell with arguments, but use the -s option to treat the arguments as positional parameters instead of a file to run.

    bash -s "some_arg"
  • Force the shell to be interactive using the -i option. (Note that this doesn't prevent the shell from exiting after the specified commands have completed, if that would have occurred without the -i option.)

    bash -i -c 'printf "flags: %s\n" "${-}"'

Testing for an interactive shell

  • Check if the shell options (-) contain the i flag.

    case "${-}" in (*"i"*)
      # Interactive shell.
    ;; (*)
      # Non-interactive shell.
    esac
  • Check if the shell prompt variable (PS1) is set.

    if [ -n "${PS1+set}" ]; then
      # Interactive shell.
    else
      # Non-interactive shell.
    fi

The difference in startup files between login and non-login shells is as follows, when not in POSIX mode.

For login shells:

For non-login shells:

Running a login shell

  • Run the shell with the --login or -l option.

    bash --login
    bash -l
  • Start a new login shell with the login command.

    login
  • Run the shell using the exec -l command.

    exec -l bash

Running a non-login shell

  • Run the shell without using any of the above.

    bash

    Alternatively, add an appropriate shebang (such as #!/usr/bin/env bash) to a shell script and invoke it directly.

    "/path/to/some_script"

Testing for a login shell

  • Check if the command (0) begins with a hyphen-minus (-).

    case "${0}" in ("-"*)
      # Login shell.
    ;; (*)
      # Non-login shell.
    esac
  • Check if the login_shell shell option is set.

    if shopt -q login_shell; then
      # Login shell.
    else
      # Non-login shell.
    fi

If POSIX mode is in effect when a shell is started, then no startup files are loaded, except for the POSIX/sh environment file (in ENV, if set) for interactive shells only.

Enabling POSIX mode

  • Run the shell as sh. In this case, some startup files are still loaded before the shell enters POSIX mode.

    sh

    Alternatively, add an appropriate shebang (such as #!/usr/bin/env sh, assuming sh is bash) to a shell script and invoke it directly.

    "/path/to/some_script"
  • Run the shell with the --posix or -o posix option.

    bash --posix
    bash -o posix
  • Run the command set -o posix or shopt -s -o posix. Note that this causes the existing shell to switch to POSIX mode, rather than starting a new shell.

    set -o posix
    shopt -s -o posix
  • Set the POSIXLY_CORRECT variable. If this is set after the shell is started, note that this causes the existing shell to switch to POSIX mode, rather than starting a new shell.

    set POSIXLY_CORRECT

Testing for POSIX mode

  • Check if the POSIXLY_CORRECT variable is set.

    if [ -n "${POSIXLY_CORRECT+set}" ]; then
      # Bash POSIX mode.
    else
      # Not POSIX mode.
    fi
  • Check if the SHELLOPTS variable contains the posix option.

    case "${SHELLOPTS}" in ("posix" | "posix:"* | *":posix:"* | *":posix")
      # Bash POSIX mode.
    ;; (*)
      # Not POSIX mode.
    esac
  • Check if the shell was run as sh.

    case $(basename "${BASH}") in ("sh" | "-sh")
      # Bash POSIX mode, by virtue of being run as `sh`.
    ;; (*)
      # Unknown - could still be in POSIX mode even if not run as `sh`.
    esac

Testing and Debugging

TODO: document how to use the included debugging scripts.

Reference

GNU Bash Documentation

GNU > Software > All GNU packages > GNU Bash > Documentation > GNU Bash manual > Bash Reference Manual:

GNU Bash Source Code

GNU > Software > How to get GNU software > Savannah > Savannah Git Hosting > bash.git:

Debian Bash Source Code

Debian Sources > bash

Apple macOS/Darwin Bash Source Code

Apple Open Source > Source Browser > bash

TODO

License

Creative Commons License

This document is licensed under a Creative Commons Attribution 4.0 International (CC-BY-4.0) License.

Accompanying source code is licensed under the MIT License.

Copyright © 2020-2021 Chris Tollefson

#!/usr/bin/env sh
# SPDX-License-Identifier: MIT
# Copyright © 2021 Chris Tollefson.
# Licensed under the MIT License (https://opensource.org/licenses/MIT).
# Debugging
# ---------
export DEBUG_SOURCE_FILE="/path/to/profile_sample.sh"
[ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ] && { [ -r "/etc/startup_debug_top.sh" ] && . "/etc/startup_debug_top.sh" || printf "error sourcing startup_debug_top.sh\n"; }
# Content
# -------
# Original content goes here.
# More debugging
# --------------
export DEBUG_SOURCE_FILE="/path/to/profile_sample.sh"
[ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ] && { [ -r "/etc/startup_debug_bottom.sh" ] && . "/etc/startup_debug_bottom.sh" || printf "error sourcing startup_debug_bottom.sh\n"; }
#!/usr/bin/env sh
# SPDX-License-Identifier: MIT
# Copyright © 2021 Chris Tollefson.
# Licensed under the MIT License (https://opensource.org/licenses/MIT).
# Displays shell debugging info, such as the name of the file, the PATH,
# whether the shell is interactive/non-interactive, login/non-login, etc.
# Intended for debugging shell startup files.
#
# NOTE: Ensure POSIX compliance for greatest portability.
#
# Requirements
# ------------
# `shellcheck` (optional, for static analysis)
# (See https://github.com/koalaman/shellcheck)
#
# Usage
# -----
# Place the startup debug files in a suitable location, for example:
# /etc/startup_debug_top.sh
# /etc/startup_debug_bottom.sh
#
# At the beginning of the very first startup file (typically /etc/profile),
# set an environment variable `DEBUG_STARTUP_FILES` to "true" to enable debugging overall:
#
#### export DEBUG_STARTUP_FILES="true"
#
# Then, for each potential startup file, set an environment variable `DEBUG_SOURCE_FILE`
# to the path and name of the containing file, immediately before sourcing BOTH
# `startup_debug_top.sh` at the top of the startup file, and `startup_debpg_bottom.sh`
# at the bottom of the startup file.
#
# For example, add the following code to the beginning of each startup file:
#
# Concise version:
#
#### export DEBUG_SOURCE_FILE="/path/to/this/file"; . "/etc/startup_debug_top.sh"
#
# Intermediate version:
#
#### export DEBUG_SOURCE_FILE="/path/to/this/file"
#### [ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ] && { [ -r "/etc/startup_debug_top.sh" ] && . "/etc/startup_debug_top.sh" || printf "error sourcing startup_debug_top.sh\n"; }
#
# Verbose version:
#
#### if [ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ]
#### then
#### export DEBUG_SOURCE_FILE="/path/to/this/file"
#### if [ -r "/etc/startup_debug_top.sh" ]
#### then
#### . "/etc/startup_debug_top.sh"
#### else
#### printf -- "/etc/startup_debug_top.sh not found or not readable\n"
#### fi
#### fi
#
# Original file content follows. Then add, for example, the following code
# to the end of each startup file:
#
# Concise version:
#
#### export DEBUG_SOURCE_FILE="/path/to/this/file"; . "/etc/startup_debug_bottom.sh"
#
# Intermediate version:
#
#### export DEBUG_SOURCE_FILE="/path/to/this/file"
#### [ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ] && { [ -r "/etc/startup_debug_bottom.sh" ] && . "/etc/startup_debug_bottom.sh" || printf "error sourcing startup_debug_bottom.sh\n"; }
#
# Verbose version:
#
#### if [ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ]
#### then
#### export DEBUG_SOURCE_FILE="/path/to/this/file"
#### if [ -r "/etc/startup_debug_bottom.sh" ]
#### then
#### . "/etc/startup_debug_bottom.sh"
#### else
#### printf -- "/etc/startup_debug_bottom.sh not found or not readable\n"
#### fi
#### fi
if [ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ]
then
# Show the `PATH` after the script has run.
printf -- " PATH = %s\n" "${PATH}"
printf -- "END: %s\n" "${DEBUG_SOURCE_FILE}"
printf -- "\n"
fi
#!/usr/bin/env sh
# SPDX-License-Identifier: MIT
# Copyright © 2021 Chris Tollefson.
# Licensed under the MIT License (https://opensource.org/licenses/MIT).
# Displays shell debugging info, such as the name of the file, the PATH,
# whether the shell is interactive/non-interactive, login/non-login, etc.
# Intended for debugging shell startup files.
#
# NOTE: Ensure POSIX compliance for greatest portability.
#
# Requirements
# ------------
# `shellcheck` (optional, for static analysis)
# (See https://github.com/koalaman/shellcheck)
#
# Usage
# -----
# Place the startup debug files in a suitable location, for example:
# /etc/startup_debug_top.sh
# /etc/startup_debug_bottom.sh
#
# At the beginning of the very first startup file (typically /etc/profile),
# set an environment variable `DEBUG_STARTUP_FILES` to "true" to enable debugging overall:
#
#### export DEBUG_STARTUP_FILES="true"
#
# Then, for each potential startup file, set an environment variable `DEBUG_SOURCE_FILE`
# to the path and name of the containing file, immediately before sourcing BOTH
# `startup_debug_top.sh` at the top of the startup file, and `startup_debpg_bottom.sh`
# at the bottom of the startup file.
#
# For example, add the following code to the beginning of each startup file:
#
# Concise version:
#
#### export DEBUG_SOURCE_FILE="/path/to/this/file"; . "/etc/startup_debug_top.sh"
#
# Intermediate version:
#
#### export DEBUG_SOURCE_FILE="/path/to/this/file"
#### [ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ] && { [ -r "/etc/startup_debug_top.sh" ] && . "/etc/startup_debug_top.sh" || printf "error sourcing startup_debug_top.sh\n"; }
#
# Verbose version:
#
#### if [ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ]
#### then
#### export DEBUG_SOURCE_FILE="/path/to/this/file"
#### if [ -r "/etc/startup_debug_top.sh" ]
#### then
#### . "/etc/startup_debug_top.sh"
#### else
#### printf -- "/etc/startup_debug_top.sh not found or not readable\n"
#### fi
#### fi
#
# Original file content follows. Then add, for example, the following code
# to the end of each startup file:
#
# Concise version:
#
#### export DEBUG_SOURCE_FILE="/path/to/this/file"; . "/etc/startup_debug_bottom.sh"
#
# Intermediate version:
#
#### export DEBUG_SOURCE_FILE="/path/to/this/file"
#### [ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ] && { [ -r "/etc/startup_debug_bottom.sh" ] && . "/etc/startup_debug_bottom.sh" || printf "error sourcing startup_debug_bottom.sh\n"; }
#
# Verbose version:
#
#### if [ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ]
#### then
#### export DEBUG_SOURCE_FILE="/path/to/this/file"
#### if [ -r "/etc/startup_debug_bottom.sh" ]
#### then
#### . "/etc/startup_debug_bottom.sh"
#### else
#### printf -- "/etc/startup_debug_bottom.sh not found or not readable\n"
#### fi
#### fi
if [ "${DEBUG_STARTUP_FILES:-'false',,}" = "true" ]
then
# Show which startup file this is.
printf -- "BEGIN: %s\n" "${DEBUG_SOURCE_FILE}"
# Show the `PATH` before the script runs.
# (See https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Variables.html#index-PATH)
printf -- " PATH = %s\n" "${PATH}"
# Show the previous working directory.
# (See https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html#index-OLDPWD)
printf -- " OLDPWD = %s\n" "${OLDPWD}"
# Show the current working directory.
# (See https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html#index-PWD)
printf -- " PWD = %s\n" "${PWD}"
# Show the path of the current bash instance.
# (See https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html#index-BASH)
printf -- " BASH = %s\n" "${BASH}"
# Show the path of the shell.
# (See https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html#index-SHELL)
printf -- " SHELL = %s\n" "${SHELL}"
# Show the command.
# (See https://www.gnu.org/software/bash/manual/html_node/Special-Parameters.html#index-_00240)
printf -- " Command: %s\n" "${0}"
# Check whether it's in `sh` mode. To do that, check if the shell is either `sh` or `-sh`.
# (See https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html#index-BASH)
case $(basename "${BASH}")
in
("sh" | "-sh")
is_sh="yes"
;;
(*)
is_sh="no"
esac
printf -- " SH: %s\n" "${is_sh}"
# Check whether it's in POSIX mode. To do that, check if the `POSIXLY_CORRECT` variable is set.
# (See https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html#index-POSIXLY_005fCORRECT)
if [ -n "${POSIXLY_CORRECT+set}" ]
then
is_posix_variable="yes"
else
is_posix_variable="no"
fi
printf -- " POSIX (variable): %s\n" "${is_posix_variable}"
# `SHELLOPTS` is bash only (not POSIX):
#### # Double-check whether it's in POSIX mode. To do that, check if the `SHELLOPTS` variable contains `posix`.
#### # (See https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html#index-SHELLOPTS
#### case "${SHELLOPTS}"
#### in
#### ("posix" | "posix:"* | *":posix:"* | *":posix")
#### # "posix" could be the only option, or
#### # "posix:" could be at the beginning, followed by other options, or
#### # ":posix:" could be in the middle, between other options, or
#### # ":posix" could be at the end of other options.
#### is_posix_option="yes"
#### ;;
####
#### (*)
#### is_posix_option="no"
#### esac
####
#### printf -- " POSIX (option): %s\n" "${is_posix_option}"
# Check whether it's a login shell. To do that, check if the first character of the command is `-`.
# (See https://www.gnu.org/software/bash/manual/html_node/Invoking-Bash.html#index-login-shell)
case "${0}"
in
("-"*)
is_login_command="yes"
;;
(*)
is_login_command="no"
esac
printf -- " LOGIN (command): %s\n" "${is_login_command}"
# `shopt` is bash only (not POSIX):
#### # Double-check whether it's a login shell. To do that, check if the `login_shell` shell option is set.
#### # (See https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html)
#### if shopt -q -- "login_shell"
#### then
#### is_login_shopt="yes"
#### else
#### is_login_shopt="no"
#### fi
####
#### printf -- "LOGIN (shopt): %s\n" "${is_login_shopt}"
# Check whether it's an interactive shell. To do that, check the `$-` variable
# to see if the `i` (interactive) flag is set in the options.
# (See https://www.gnu.org/software/bash/manual/html_node/Special-Parameters.html#index-_0024_002d)
case "${-}"
in
(*"i"*)
is_interactive_option="yes"
;;
(*)
is_interactive_option="no"
;;
esac
printf -- " INTERACTIVE (option): %s\n" "${is_interactive_option}"
# Double-check whether it's an interactive shell. To do that, check the `$PS1` variable,
# to see if the primary prompt string is set for an interactive shell.
# (See https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Variables.html#index-PS1)
if [ -n "${PS1+set}" ]
then
is_interactive_prompt="yes"
else
is_interactive_prompt="no"
fi
printf -- " INTERACTIVE (prompt): %s\n" "${is_interactive_prompt}"
printf -- "\n"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment