This is most likely a specific instance of the issue described in "Why is my BASH_FUNC_foobar%% environment variable unset in shell subprocesses?".
When you export a function in bash
, it creates an environment variable with a special name:
$ foo () { echo hello; }
$ export -f foo
$ env
...
BASH_FUNC_foo%%=() { echo hello
}
...
The shell does this because shell functions can't really be exported as functions, so they are converted to "special environment variables" instead. Environment variables can only ever be simple key-value string pairs.
When a bash
shell inherits an environment with these sorts of environment variables, it know that they are bash
functions, and instantiates the functions with the appropriate names.
According to the POSIX standard:
Environment variable names used by the utilities in the Shell and Utilities volume of POSIX.1-2017 consist solely of uppercase letters, digits, and the <underscore>
(_
) from the characters defined in Portable Character Set and do not begin with a digit. Other characters may be permitted by an implementation; applications shall tolerate the presence of such names. Uppercase and lowercase letters shall retain their unique identities and shall not be folded together. The name space of environment variable names containing lowercase letters is reserved for applications. Applications can define any environment variables with names from this name space without modifying the behavior of the standard utilities.
According to this passage, environment variables containing %
in their names are permitted, but other shells, for example the shells that masquerade as /bin/sh
on some systems (dash
on Ubuntu for example, and ksh
on OpenBSD), sanitises the environment and removes any environment variables whose names contain characters other than the explicitly allowed ones.
The /bin/sh
shell is used when an application calls system()
to start another process.
This all means that if your /bin/sh
is dash
(which it is on Ubuntu), and if the environment of the final interactive bash
shell that you get in a terminal was ever passed (via inheritance from parent process to child process) through a call to system()
, or in some other way was inherited by /bin/sh
on the way, then your functions would have disappeared1.
The workaround is to define your functions in ~/.bashrc
or wherever you define your aliases. Or to get your terminal to spawn a bash
login shell.
1Unfortunately, I don't run either of GDM or Ubuntu, so I can't currently run strace
on the processes involved in the login procedure to see what actually happens there.
Example showing a function disappearing between one bash
shell and another, when the other bash
shell is invoked by dash
:
$ foo () { echo hello; }
$ export -f foo
$ dash -c 'bash -c foo'
bash: foo: command not found
Example of using yash
instead, which does not remove the function:
$ yash -c 'bash -c foo'
hello
Likewise, ksh
on OpenBSD sanitises, while ksh93
and zsh
do not:
$ ksh -c 'bash -c foo'
bash: foo: command not found
$ ksh93 -c 'bash -c foo'
hello
$ zsh -c 'bash -c foo'
hello
Note that in all cases where the output is hello
above, the intermediate shell is totally unaware that the specially named environment variable constitutes a function:
$ yash -c 'foo'
yash: no such command `foo'
$ ksh93 -c 'foo'
ksh93: foo: not found
$ zsh -c 'foo'
zsh:1: command not found: foo
.profile
though? There are a few ordinary shells will read the file when invoked as a login shell. Do you know for a fact that GDM starts abash
login shell, and not ash
login shell?bash
(specified in/etc/passwd
) and the shebang of/etc/gdm3/Xsession
is#!/bin/bash
. I also confirmed that bash is being used to source.profile
by printing$BASH_VERSION
to a file.bash
reads the file. Good. The next thing to note is that an exported function is likely only usable in abash
shell child process. I've just tested exporting a function, starting another (non-bash
) shell, and the function is not there. Nor is it there if I startbash
from that other shell. It is there if I start abash
shell from the first shell. This means that if you start an interactivebash
shell from something that is notbash
, the function may not be in the environment.dash
(orsh
, on Ubuntu), orsystem()
, will strip out the exported functions' environment variables.gnome-session
script has a/bin/sh
shebang, which means that it will be run withdash
, which will wipe off all funny environment variables, as described in Michael Homer's comment. You may change that shebang by hand to#! /bin/bash
(not really recommended), but it will be changed back at the next upgrade of thegnome-session-bin
package.