I am using su
to start tmux
as a specific user, like this:
$ su - someuser -c tmux
However, this results in an error message:
tmux: need UTF-8 locale (LC_CTYPE) but have ANSI_X3.4-1968
The error occurs because the LANG
environment variable is not set. A new login shell normally sets this via /etc/profile
but the invocation through su
does not use /etc/profile
even though the use of su -
does request and appear to produce a login shell.
The user someuser
that this does not work for has the bash
shell. However, it works with other shells such as dash
or zsh
. An interactive bash
login shell also works as expected.
Why does bash
ignore /etc/profile
when when invoked non-interactively through su -
?
My research (below) would indicate that the problem is that bash
, in contrast to other shells, behaves differently when a login shell is requested with a leading hyphen in $0
(what su
does) versus its -l
(or --login
) command-line option.
Research
(this is somewhat verbose but I wanted to record my investigations)
man su
explains that the -c
argument is used to
Pass command to the shell with the -c option.
and the -
argument
Start the shell as a login shell with an environment similar to a real login
So su - someuser -c cmd
should execute cmd
in a login shell for someuser
with an environment similar to a real login.
A test command is useful to demonstrate:
$ testcmd='shopt -q login_shell || echo -n non-; echo login shell $0 $LANG'
Trying this with su
for login and non-login shells:
$ su - someuser -c "$testcmd"
login shell -bash
$ su someuser -c "$testcmd"
non-login shell bash en_GB.UTF-8
When a login shell is launched, the environment is not configured (LANG
is not set because /etc/profile
is not used). The environment is inherited from when a non-login shell is launched so the value of LANG
is that from the launching shell, as clearing it beforehand demonstrates:
$ LANG= su someuser -c "$testcmd"
non-login shell bash
In the above examples, someuser
has the bash
shell. The same results are obtained with sh
(which is just a symlink to bash
). If zsh
is used instead then things work as expected:
$ ztestcmd='[[ -o login ]] || echo -n non-; echo login shell $LANG'
$ LANG= su - someuser -s /bin/zsh -c "$ztestcmd"
Password:
login shell -zsh en_GB.UTF-8
$ LANG= su someuser -s /bin/zsh -c "$ztestcmd"
Password:
non-login shell zsh
su someuser -s /bin/zsh -c "$ztestcmd"
Password:
non-login shell zsh en_GB.UTF-8
It also works with dash
. So the issue would appear to be with bash
. Delving into bash
...
It works as expected if an interactive shell is launched:
$ su - someuser
$ LANG= shopt -q login_shell || echo -n non-; echo login shell $0 $LANG
login shell -bash en_GB.UTF-8
So interactive and non-interactive bash
launched through su
behave differently. What about launching bash
directly?
$ LANG= bash -c "$testcmd"
non-login shell bash
$ LANG= bash -l -c "$testcmd"
login shell bash en_GB.UTF-8
That works as expected.
The GNU Bash Manual states that
When Bash is invoked as an interactive login shell, or as a non-interactive shell with the
--login
option, it first reads and executes commands from the file/etc/profile
According to man su
, its -
option does not use --login
but instead
sets argv[0] of the shell to
-
in order to make the shell a login shell
Which can be proven:
$ su - someuser -c 'echo $0'
Password:
-bash
We can test ourselves that a leading hyphen in $0
gives a login shell:
$ (LANG= exec -a '-' bash -c "$testcmd")
login shell -
So a leading hyphen in $0
produces a login shell but bash
does not execute /etc/profile
when invoked in this way which is inconsistent with the behaviour of its -l
option and also with other shells such as zsh
or dash
. It's also inconsistent with its own behaviour when launched interactively:
$ (LANG= exec -a '-' bash)
$ shopt -q login_shell || echo -n non-; echo login shell $0 $LANG
login shell - en_GB.UTF-8
Unfortunately this isn't in conflct with the documentation excerpted above because that doesn't define the behaviour of a non-interactive login shell that was not launched with -l
.
/etc/environment
(caveat: the surprising syntax)