Bash in Fedora
Bash is the default shell in Fedora. The distribution ships it with sensible presets and additional functionality. This article discusses the relevant configuration files and explains how they can be customized.
Personally I use the Fedora Sway Spin. I wasn’t able to find a repository for the Workstation configuration, but I would assume that the Bash settings are essentially the same for both variants of the distribution. If there are important differences, I would appreciate feedback via email or Mastodon!
The Login Shell and Modern Modes
The main purpose of a shell is to execute applications. A text-based shell runs in a so-called terminal and processes user input entered via the keyboard.
Additionally, for system administration and automation, it’s possible to combine commands into scripts. The process is facilitated by programming constructs such as control structures and variables that are an integral part of the shell language. Scripts behave as self-contained programs and can be parametrized to influence behavior when it’s called from the command line.
For ordinary purposes, shell scripts should be written so that they behave in exactly the same way when executed on different machines. Instead of loading configuration files, the execution environment is defined within the script itself. In the following, I’ll only talk about the interactive use of the shell.
Traditionally, computers were operated entirely through text. Users would log into the operating system and then execute commands via a shell, possibly with inputs through a physical terminal. Terminals as standalone applications (or even multiplexers like Screen) didn’t exist yet; users were limited to the one shell (or shell instance) that greets them after login.
Even back then, there were tools that took up the whole terminal real estate.1 Naturally, you wouldn’t want to close the running application to execute shell commands. Background processes were not yet part of the OS, either. For that reason, programs like ex
and vi
provided an :sh
command that would call up a nested shell. Login shell was coined to refer to the embedding process.
In modern systems the procedure is somewhat similar, since users are generally first prompted to log in with their name and password. Fedora uses SDDM for this stage of the boot sequence. However, this wouldn’t start an interactive shell but a graphical session with the desktop environment or window manager that was selected in the previous step. Terminals still exist, but they’ve changed in nature. Today they are window-based applications on a par with other graphical user interfaces. Unlike the hardware they emulate, the can run multiple instances of the same shell.
When a shell is started with a terminal emulator, it is usually not a login shell in the traditional sense. To still be able to play this role under certain circumstances, Bash can be executed in the login mode. There is the --login
flag to manually evoke Bash so that it would simulate the login-shell behavior.
To my mind, it’s no longer obvious which contexts involve a login in the relevant sense. For this reason, it’s harder to decide what special requirements should be placed on the configuration for these environments. Intuitively, the login mode is used for general system administration purposes, and we’ll see that the Fedora configuration reflects this preconception. For a paradigmatic example, think of logging into a system via an SSH connection.
Just as you specify which shell a terminal emulator should use, you can choose the login shell for users of the operating system. Distributions typically use Bash, as does Fedora. But it’s of course possible to set Zsh or Fish, for instance:
sudo chsh --shell <path-to-shell> <user-name> # See /etc/shells for valid options
Bash can run in modes not mentioned here, for instance with POSIX compatibility. The modes are not mutually exclusive: a login shell, for example, is typically operated interactively. The main distinction outlined in this section is special, because different configuration files are sourced depending on the mode in which Bash is running.
In the following sections, I’ll first present an overview of the relevant files and their order of execution. I’ll then discuss the presets you would find in a fresh Fedora installation.
Bash Startup Files
As mentioned in the previous section, Bash
automatically loads some configuration files. A distinction must be made between global and user-specific shell settings. The latter are found in the home directory, the former under /etc/
.
The Bash man page explains:
/etc/profile
: “The systemwide initialization file, executed for login shells”~/.bash_profile
: “The personal initialization file, executed for login shells”~/.bashrc
: “The individual per-interactive-shell startup file”/etc/bash.bash_logout
: “The systemwide login shell cleanup file, executed when a login shell exits”~/.bash_logout
: “The individual login shell cleanup file, executed when a login shell exits”
The startup behavior can be suppressed by --noprofile
and --norc
flags, respectively. This affects both the user-specific and the system-wide configuration. With --rcfile
, the path to a configuration file can be specified, for instance to use the ~/.config
directory (as suggested by the
XDG standard).
As we’ll see, Fedora uses /etc/bashrc
as system-wide configuration file similar to the login-shell case. The file is not mentioned in the man page, and neither is /etc/bash.bashrc
(as used by other distributions). I’ll talk about its intended use below, but it might be noted that it’s not part of the offical standard.
It might also be worth noticing that the system-wide profile
is sourced even if the local .bash_profile
exists. So it doesn’t just serve as a fallback. .bash_login
or .profile
are possible user-level alternatives, but they are mutually exclusive and .bash_profile
takes precedence.
As the man page explains, logout
files are used to automatically perform some cleanup tasks at the end of a login-shell session. Fedora doesn’t include these files, which is why I won’t discuss them further.
Startup Files in Fedora
In Fedora, additional files are sourced with Bash by default. The most important difference is that an additional system-wide /etc/bashrc
is defined and explicitly imported at the beginning of the user-specific .bashrc
. Thus, as with the login shell, the system-wide configuration is loaded first and the user-specific configuration afterwards.
In the configuration for login mode, the settings of a regular terminal session are loaded by importing the local .bashrc
in .bash_profile
and the global bashrc
in /etc/profile
. This is different from Bash’s standard behavior, where the configuration of login shells is done independently.
Thus, in a login shell the user has available the same aliases and functions that are familiar from other contexts. If environment variables are set in RC files, this extends or modifies the profile environment.
A safeguard mechanism ensures that the global RC file is loaded only once in a Bash session:
# /etc/bashrc
# Prevent doublesourcing
if [ -z "$BASHRCSOURCED" ]; then
BASHRCSOURCED="Y"
Without this check, it would be executed in the login context first system-wide (in /etc/profile
) and then locally (in .bash_profile
).
In both modes, additional configuration scripts (with an *.sh
extension) are placed in and sourced from /etc/profile.d/
. Contrary to what the naming scheme might suggest, they are manually imported in both cases, i.e. in /etc/profile
and /etc/bashrc
.
This directory is also intended for your own system-wide configurations, for example in /etc/profile.d/custom.sh
. ~/.bashrc.d/
is intended for local configuration modules of a regular shell session. Its scripts are imported in ~/.bashrc
, but the directory itself does not exist in Fedora by default.
Intended Purpose of Use
As explained, different configuration files are automatically loaded depending on Bash’s mode of execution. In addition to this, Fedora provides guidelines on what types of settings should be placed in each of them.
The relevant comments are included as headers for /etc/profile
and /etc/bashrc
:
profile
is for “System wide environment and startup programs, for login setup”, i.e., for the definition of environment variables and programs that should be executed automatically when Bash starts; andbashrc
is for “system wide functions and aliases”.
As “for login setup” indicates, the distinction is only assumed for the login context. For a regular terminal session, the environment defined in /etc/profile
files wouldn’t apply without sourcing it explicitly.
Perhaps the distinction is based on the principle that a system-wide environment should only be defined for a login shell, while the specific enfironment for a regular terminal session is best defined at the user level. If so, the rationale for this principle seems reasonable though overly complicated.
Maybe it’s best to keep things simple and exclusively use the user-specific .bashrc
(with bashrc.d
) for your own configurations, especially on systems with only one active user. If you see no point in the outdated idea of a login shell, you don’t need to touch the local .bash_profile
at all. In Fedora, it (only) imports the .bashrc
, projecting its settings to the login context.
The system-wide /etc/profile
and /etc/bashrc
should only be edited in very special cases, as emphasized in a warning at the beginning of the files. Fedora maintainers may make changes to their content at a later point, in which case future updates of the distribution would overwrite your own modifications.
Fedora Presets
The files mentioned so far contain many sensible default settings, which I’ll address in this section. They concern not only Bash itself but also the behavior of certain CLI applications.
In the following subsections, code listings are included. In two or three places, I add less significant remarks as comments. The listings are structured thematically and do not correspond to the exact sequence of the files under discussion.
Environment
When a user starts Bash in a terminal emulator, only ~/.bashrc
is sourced by default. We’ve already seen that in Fedora, additional files are included:
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# User specific aliases and functions
if [ -d ~/.bashrc.d ]; then
for rc in ~/.bashrc.d/*; do
if [ -f "$rc" ]; then
. "$rc"
fi
done
fi
unset rc
Other than that, it only contains the addition of two home directories to the path, namely ~/.local/bin/
and ~/bin/
:
# User specific environment
if ! [[ "$PATH" =~ "$HOME/.local/bin:$HOME/bin:" ]]; then
PATH="$HOME/.local/bin:$HOME/bin:$PATH"
fi
export PATH
As the heading indicates, user-specific environment variables may be defined here. An additional export
is necessary so that they are also available in child processes of the shell - scripts and other programs called by it.
For a login shell, an environment with system information is established. It’s defined in the system-wide profile
file:
if [ -x /usr/bin/id ]; then
if [ -z "$EUID" ]; then
# ksh workaround
EUID=`/usr/bin/id -u`
UID=`/usr/bin/id -ru`
fi
USER="`/usr/bin/id -un`"
LOGNAME=$USER
MAIL="/var/spool/mail/$USER"
fi
HOSTNAME=$(/usr/bin/hostnamectl --transient 2>/dev/null) || \
HOSTNAME=$(/usr/bin/hostname 2>/dev/null) || \
HOSTNAME=$(/usr/bin/uname -n)
export PATH USER LOGNAME MAIL HOSTNAME
The path for this context is defined with a helper function:
pathmunge () {
case ":${PATH}:" in
*:"$1":*)
;;
*)
if [ "$2" = "after" ] ; then
PATH=$PATH:$1
else
PATH=$1:$PATH
fi
esac
}
# Path manipulation
if [ "$EUID" = "0" ]; then
pathmunge /usr/sbin
pathmunge /usr/local/sbin
else
pathmunge /usr/local/sbin after
pathmunge /usr/sbin after
fi
unset -f pathmunge
The sbin
directories contain binary files for tools used for system administration. These include applications like ipconfig
, fdisk
, or shutdown
, which should be readily available in a login context.
Interestingly, the sbin
components are also found on the path of a regular terminal session. I couldn’t find an entry in the RC files responsible for this behavior.
To make your own changes to the global environment of the login shell, the file /etc/profile.d/sh.local
is included. In a newly set up system, it contains only a comment:
#Add any required envvar overrides to this file, it is sourced from /etc/profile
Here you can define environment variables safe from possible overwrites through distribution updates.
Prompt
In a shell, symbols like $
or >
indicate that user input is expected. This aspect of what is visible in a terminal window is referred to as a prompt.
In Bash, the prompt by default contains additional components that provide some information about the running system, for example:
bash-5.2$
Visual appearance and content of the prompt can be customized. Fedora overwrites the Bash preset:
# Change the default prompt string
[ "$PS1" = "\\s-\\v\\\$ " ] && PS1="[\u@\h \W]\\$ "
The PS1
environment variable is used to specify the format of the (primary) prompt using special escape sequences. The first string codifies the standard format exemplified above, consisting of shell name (\s
), shell version (\v
), and prompt character (the literal $
).
The line as a whole implies: if the default format is used, use the format in the second string instead. \u
stands for username, \h
for hostname, and \W
for an abbreviated form of the current working directory. Thus, the prompt might look like:
[kai@fedora-laptop www]$
With the PROMPT_COMMAND
environment variable, a command can be set that is executed before each display of the prompt. In Fedora, it is defined as follows:
# are we an interactive shell?
if [ "$PS1" ]; then
if [ -z "$PROMPT_COMMAND" ]; then
declare -a PROMPT_COMMAND
case $TERM in
xterm*)
if [ -e /etc/sysconfig/bash-prompt-xterm ]; then
PROMPT_COMMAND=/etc/sysconfig/bash-prompt-xterm
else
PROMPT_COMMAND='printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/\~}"'
fi
;;
screen*)
if [ -e /etc/sysconfig/bash-prompt-screen ]; then
PROMPT_COMMAND=/etc/sysconfig/bash-prompt-screen
else
PROMPT_COMMAND='printf "\033k%s@%s:%s\033\\" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/\~}"'
fi
;;
*)
[ -e /etc/sysconfig/bash-prompt-default ] && PROMPT_COMMAND=/etc/sysconfig/bash-prompt-default
;;
esac
fi
fi
Following a standard, scripts under /etc/sysconfig/
are executed as prompt commands if they exist. Here, a distinction is made between a regular terminal (the value of $TERM
starts with xterm*
) and a
terminal multiplexer (the value of $TERM
starts with screen*
).
Fedora doesn’t come with these files included. Instead, a printf
command with invisible output is used to set the title of the terminal window. If the application executed in the foreground changes, the window title also changes dynamically each time the prompt reloads. The command could also be used to visibly display information in the line above the prompt. The variable can be overwritten at the user level, as when using the
Starship prompt.
Under /etc/profile.d/bash-color-prompt.sh
, there is a script that can be used to color-code components of the prompt. An explanation of its use can be found under /usr/share/doc/bash-color-prompt/README.md
.
Internationalization
Some CLI applications base their internationalization (i18n) on the values of certain environment variables. This is the case for
GNU AWK, for example. Fedora uses /etc/profile.d/lang.sh
to ensure proper functioning through sensible fallbacks.
The locale
command can be utilized to check whether the LC_*
environment variables have been properly defined (for their intended purpose, see the
Arch Wiki). Localization problems may arise if the command returns any errors. In this case, the variables are reset to their default values in the lang.sh
:
# If unavailable, reset to the default. Do this before reading in any
# explicit user configuration. We simply check if locale emits any
# warnings, and assume that the settings are invalid if it does.
if [ -n "$(/usr/bin/locale 2>&1 1>/dev/null)" ]; then
[ -z "$LANG" ] || LANG=C.UTF-8
unset LC_ALL # If it were defined, it would override the other values
LC_CTYPE="C.UTF-8"
LC_NUMERIC="C.UTF-8"
LC_TIME="C.UTF-8"
LC_COLLATE="C.UTF-8"
LC_MONETARY="C.UTF-8"
LC_MESSAGES="C.UTF-8"
LC_PAPER="C.UTF-8"
LC_NAME="C.UTF-8"
LC_ADDRESS="C.UTF-8"
LC_TELEPHONE="C.UTF-8"
LC_MEASUREMENT="C.UTF-8"
LC_IDENTIFICATION="C.UTF-8"
fi
Next, the environment is established as defined in two standard configuration files: /etc/locale.conf
and ~/.i18n
. The latter file does not exist in Fedora by default, the former contains only a single variable definition. Its value depends on what was set during the installation process, for example:
LANG="en_US.UTF-8"
The import involves a security mechanism:
for config in /etc/locale.conf "${HOME}/.i18n"; do
if [ -f "${config}" ]; then
# NOTE: We are using eval & sed here to avoid invoking of any commands & functions from those files.
if [ -x /usr/bin/sed ]; then
eval $(/usr/bin/sed -r -e 's/^[[:blank:]]*([[:upper:]_]+)=([[:print:][:digit:]\._-]+|"[[:print:][:digit:]\._-]+")/export \1=\2/;t;d' ${config})
else
#but if we don't have sed, let's go old way and source it
[ -f "${config}" ] && . "${config}"
fi
fi
done
This ensures that only lines in the variable=value
format are imported. This processing method prevents Bash from unintentionally executing (other) commands. Should sed
not be available for whatever reason, the content would be sourced directly in the more usual way.
Another safeguard mechanism in the lang.sh
concerns the LC_ALL
environment variable. Since it would overwrite all other LC_*
values, it should only be set deliberately. This is ensured in the following way:
# The LC_ALL is not supposed to be set in /etc/locale.conf according to 'man 5 locale.conf'.
# If it is set, then we we expect it is user's explicit override (most likely from ~/.i18n file).
# See 'man 7 locale' for more info about LC_ALL.
if [ -n "${LC_ALL}" ]; then
if [ "${LC_ALL}" != "${LANG}" -a -n "${LANG}" ]; then
export LC_ALL
else
unset LC_ALL
fi
fi
A final measure concerns so-called
virtual consoles, the heirs to the real login shell. They are started with the operating system, and we can enter them with Ctrl+Alt+F<1-9>
. They are special since they are also not graphical in nature. That could lead to issues with languages that use certain special character sets, like Japanese, Korean, or Arabic.
# The ${LANG} manipulation is necessary only in virtual terminal (a.k.a. console - /dev/tty*):
if [ -n "${LANG}" ] && [ "${TERM}" = 'linux' ] && /usr/bin/tty | /usr/bin/grep --quiet -e '/dev/tty'; then
if /usr/bin/grep --quiet -E -i -e '^.+\.utf-?8$' <<< "${LANG}"; then
case ${LANG} in
ja*) LANG=en_US.UTF-8 ;;
ko*) LANG=en_US.UTF-8 ;;
si*) LANG=en_US.UTF-8 ;;
zh*) LANG=en_US.UTF-8 ;;
ar*) LANG=en_US.UTF-8 ;;
fa*) LANG=en_US.UTF-8 ;;
he*) LANG=en_US.UTF-8 ;;
en_IN*) true ;;
*_IN*) LANG=en_US.UTF-8 ;;
esac
else
case ${LANG} in
ja*) LANG=en_US ;;
ko*) LANG=en_US ;;
si*) LANG=en_US ;;
zh*) LANG=en_US ;;
ar*) LANG=en_US ;;
fa*) LANG=en_US ;;
he*) LANG=en_US ;;
en_IN*) true ;;
*_IN*) LANG=en_US ;;
esac
fi
# NOTE: We are not exporting the ${LANG} here again on purpose.
# If user starts GUI session from console manually, then
# the previously set LANG should be okay to use.
fi
To avoid weird characters in virtual consoles, the language settings are set to English in these cases.
Bash Options
Bash itself can be configured via environment variables and via some options that can be set with the built-in shopt -s
command. The available options and their set values can be displayed by calling shopt
without arguments.
It may be argued what options should be in place as sensible defaults. Fedora remains largely neutral on the issue and activates only two such settings:
# /etc/bashrc
# Turn on parallel history
shopt -s histappend
# Turn on checkwinsize
shopt -s checkwinsize
checkwinsize
ensures that shell print-out remains legible despite possible changes to the size of the containing terminal window. So, it’s the kind of option that make you go, who wouldn’t want that?
histappend
concerns the case where multiple Bash sessions are started simultaneously. Without the shell behaves in such a way that each newly started session overwrites the history file (~/.bash_history
). Thus, changes to the hitory (file) would only depend on the last session and its most recent commands. The given option causes the different sessions to append their commands to the history.
The system-wide profile includes two more settings that concern the Bash history:
# /etc/profile
if [ -z "$HISTSIZE" ] ; then
HISTSIZE=1000 # maximum number of commands stored
fi
if [ "$HISTCONTROL" = "ignorespace" ] ; then
export HISTCONTROL=ignoreboth
else
export HISTCONTROL=ignoredups
fi
export HISTSIZE HISTCONTROL
The condition concerns ignorespace
and ignoredups
. The latter option prevents duplicates: If an executed command is already in the history, it is appended at the end and the earlier entry is removed. The ignorespace
option gives users a simple way to execute a command without adding it to the history, namely by prefixing a space.
Both branches imply that the history should not contain duplicates. In the second case the requirement is explicitly stated. In the first case we have the same result, because ignoreboth
comprises both ignorespace
and ignoredups
.
It’s probably reasonable to add checkwinsize
and ignoredups
to your RC configuration, too. You may also want to set some of the following options:
shopt -s cmdhist # Store multi-line commands as single history entry
shopt -s xpg_echo # Allow escape sequences like `\n` in `echo` (POSIX standard)
shopt -s nullglob # Unmatched globbing patterns expand to nothing (instead of the pattern)
shopt -s nocaseglob # Case-insensitive globbing
shopt -s autocd # Change directory without prepended `cd`
shopt -s cdspell # Bash will fix minor typos in `cd` commands
Aliases
In Fedora, common shell commands like grep
and ls
are displayed in color. Their respective man pages reveal that this can be done by certain command flags. Interestingly, the result doesn’t depend on actually using those flags. Moreover, additional commands are available, like ll
for an ls
list view.
The reason for these behaviors is that some aliases reinterpret existing commands. To list them, use alias
without any arguments:
> alias
alias egrep='grep -E --color=auto'
alias fgrep='grep -F --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias which='(alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot'
alias xzegrep='xzegrep --color=auto'
alias xzfgrep='xzfgrep --color=auto'
alias xzgrep='xzgrep --color=auto'
alias zegrep='zegrep --color=auto'
alias zfgrep='zfgrep --color=auto'
alias zgrep='zgrep --color=auto'
The definitions can be found in modules under /etc/profile.d/
. For example, grep
appears color-coded due to the following setting:
# /etc/profile.d/colorgrep.sh
/usr/libexec/grepconf.sh -c || return
alias grep='grep --color=auto' 2>/dev/null
alias egrep='grep -E --color=auto' 2>/dev/null
alias fgrep='grep -F --color=auto' 2>/dev/null
The file is also responsible for the fact that egrep
and fgrep
are available. Similar settings for zgrep
, xzgrep
, and ls
can be found in similar modules. colorls.sh
also contains definitions for ll
and l.
.
There are other standard tools with a color option. For example, one could add the following file:
# /etc/profile.d/colorip.sh
alias ip='ip -color=auto'
Another alias confers extended functionality to the which
command (depending on the shell used):
# Initialization script for bash, sh, mksh and ksh
case "$(basename $(readlink /proc/$$/exe))" in
*ksh*|zsh)
alias which='alias | /usr/bin/which --tty-only --read-alias --show-tilde --show-dot'
;;
bash|sh)
alias which='(alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot'
;;
*)
;;
esac
Normally, which
only recognizes binary files on the path. This redefinition first checks whether an alias exists with the name in question. For this reason, which ll
is able to show the corresponding alias definition; the ordinary which
would merely have pointed out that ll
is not on the path.
Configuration for other CLI Applications
There are other application-specific scripts under /etc/profile.d/
. For example, gawk.sh
defines which libraries are available in AWK without having to enter the full path.
GAWKPATH and
GAWKLIBPATH work very similarly to the shell’s PATH
variable.
less.sh
is responsible for ensuring that less
can “open” not only text files but also binary files of certain types.
# less initialization script (sh)
if [ -z "$LESSOPEN" ] && [ -x /usr/bin/lesspipe.sh ]; then
# The '||' here is intentional, see rhbz#1254837.
export LESSOPEN="||/usr/bin/lesspipe.sh %s"
fi
With LESSOPEN
, a command is defined that is executed prior to each less
call. The lesspipe.sh
filter determines what should happen depending on the file type. Among other things, the script makes it possible to use less
to list the contents of archives or to show meta-information of images.
The application-related scripts can be very simple. nano-default-editor.sh
is responsible for setting Nano as the value for EDITOR
, if the variable has not already been defined or is overwritten later. You may argue that it’s not the most obvious way to set what editor to use. The idea is probably that nano
is the most user-friendly text editor included with the distribution (more so than vi
). Individual users may either use that, or install their preferred option and redefine EDITOR
in their own user-level configuration.
gnupg2.sh
is another one-liner, but its effect is less obvious:
export GPG_TTY=$(tty)
The tty
command returns the path to the terminal to which the standard output is currently connected. The GPG_TTY
variable defines which TTY should be used by
pinentry when the user is required to enter a passphrase (in the little GUI window you may be familiar with). This prevents errors that otherwise may occur in certain environments (such as SSH or TMUX sessions).
Finally, debuginfod.sh
is a script that provides settings for the service of the same name. I won’t pretend that I understand what’s going on in there.
Autocompletion
Bash is shipped by Fedora with the ability to Tab complete inputs. It may come as a surprise that autocompletion is not one of Bash’s built-in features.
The complex functionality is
implemented in /usr/share/bash-completion/bash_completion
and sourced in /etc/profile.d/bash_completion.sh
. Fedora comes with completions for more than a thousand applications, located under /usr/share/completions/
.
New entries can be added to this directory, especially for newer or less widespread applications. The
Yazi download, for example, includes completions for its two components (yazi
and ya
). Their file extension indicates which shell they are intended for.