export -f func I can see the following in the environment:
$ env | grep func
Bash exports functions as variables with names like
BASH_FUNC_foo%% and values starting with
(). I believe before Shellshock names were without any prefix (
foo=() …). Apple prefix seems to be
__BASH_FUNC<. In any case the value starts with
sudo tries to be very careful with these names. First of all, when I run
sudo -V as root (
sudo sudo -V as regular user), a part of the output is like:
Environment variables to remove:
Environment variables to remove:
depending on the version of
sudo, I think.
I can remove these patterns from the list with the following line in
sudoers (but don't do this yet, read the whole answer first):
Defaults env_delete -= "*=()* BASH_FUNC_* __BASH_FUNC<*"
And I can even add them to the "preserve" list (also don't):
Defaults env_keep += "*=()* BASH_FUNC_* __BASH_FUNC<*"
Still it will not necessarily make your exported function work with
sudo. The reason is the pattern of
*=()* might be hardcoded, some versions of
sudo reject variables with values starting with
(). This behavior evolved. Available solutions depend on
sudo version you have.
man 5 sudoers:
Since environment variables can influence program behavior,
sudoers provides a means to restrict which variables from the user's environment are inherited by the command to be run. There are two distinct ways sudoers can deal with environment variables.
By default, the
env_reset option is enabled. […] The
USER environment variables are initialized based on the target user and the
SUDO_* variables are set based on the invoking user. Additional variables, such as
TERM, are preserved from the invoking user's environment if permitted by the
env_keep options. This is effectively a whitelist for environment variables. […] Environment variables with a value beginning with
() are removed unless both the name and value parts are matched by
env_check, as they may be interpreted as functions by the bash shell. Prior to version 1.8.11, such variables were always removed.
If, however, the
env_reset option is disabled, any variables not explicitly denied by the
env_delete options are inherited from the invoking process. In this case,
env_delete behave like a blacklist. Prior to version 1.8.21, environment variables with a value beginning with
() were always removed. Beginning with version 1.8.21, a pattern in
env_delete is used to match bash shell functions instead. Since it is not possible to blacklist all potentially dangerous environment variables, use of the default
env_reset behavior is encouraged.
Environment variables specified by
env_keep may include one or more
* characters which will match zero or more characters. No other wildcard characters are supported.
By default, environment variables are matched by name. However, if the pattern includes an equal sign (
=), both the variables name and value must match. For example, a bash shell function could be matched as follows:
env_keep += "BASH_FUNC_my_func%%=()*"
=()* suffix, this would not match, as bash shell functions are not preserved by default.
As you can see
env_reset is important. You most likely have it enabled. Note the whole point of the
-E option (
sudo -E) is to disable
env_reset on demand.
In my Kubuntu 18.04.3 LTS I have
sudo in the version 1.8.21. By default it places
*=()* on the list of variables to remove. This line in
sudoers makes it stop:
Defaults env_delete -= "*=()*"
sudo -E will preserve any exported shell function. This is my primary answer.
In my Debian 9
sudo is in the version 1.8.19. By default it places
__BASH_FUNC<* on the list of variables to remove. Removing these entries doesn't work because the tool almost always rejects variables with values beginning with
() anyway; this behavior seems to be hardcoded.
The manual for this version gives some clues. I found one solution:
Defaults env_keep += "BASH_FUNC_func%%=()*"
-E will preserve this particular shell function (
func) if exported. Disadvantages (or maybe advantages in some circumstances):
- this doesn't work with
sudo -E, so I cannot simply keep the rest of environment on demand;
- I need to explicitly specify the whole name of the variable, without wildcards;
*=()* will not work.
This approach works in 1.8.21 as well.
According to the manual, prior to version 1.8.11 variables starting with
() were always removed. If your
sudo is that old then there is no solution other than the trick you used in the question (or similar).
- Do not edit
sudoers like any other file; use
visudo. Other precautions just in case:
- Start a separate "spare" shell as root (
sudo su -). If you do something stupid with
sudoers in your main shell, you will still be able to act as root without relying on the file.
- When connecting via SSH, use
screen before spawning the elevated shell. If you do something stupid with
sudoers and (accidentally) the connection fails, the elevated shell will wait for your regular user to reattach, instead of exiting.
- Make a backup copy of the file first (
cp -a), especially if you're going to experiment with it.
- When you think you're done, make sure you can gain the root access anew before you exit the "spare" elevated shell.
sudo to preserve any shell function may make it easy for you to shoot yourself in the foot. There are reasons the tool rejects functions by default.