2

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.

  • Try the more dependable /etc/environment (caveat: the surprising syntax) – kubanczyk Jan 10 '17 at 12:44
2

It does what it says on the tin.

Recall the documentation:

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

It explains correctly that it reads and executes /etc/profile as an interactive login shell or as a non-interactive shell with the --login option. It does not say that it does so as a non-interactive login shell indicated by $0 beginning with a hyphen.

However, this behaviour is by design and it is something that can be changed at compile time by uncommenting a compile-time option found in config-top.h:

/* Define this to make non-interactive shells begun with argv[0][0] == '-'
run the startup files when not in posix mode. */
/* #define NON_INTERACTIVE_LOGIN_SHELLS */

This does beg the question, however, why this behaviour was implemented when it isn't, IMHO, how one would reasonably expect it to work, especially when it isn't implemented like that in other shells.

That said, I don't think the POSIX standard shell defines login shell so I guess it's open to interpretation.

Here is a workaround to the original problem that works with bash:

$ su - someuser -c 'bash -l -c tmux'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.