3

I have some Python code that does: (this is a simplified version)

shell = os.environ['SHELL']
os.environ['PATH'] = ... # some new PATH value
if call(shell, '-c', 'program_that_checks_that_PATH_is_sane'): # hasn't been mangled by the rcfile
    sys.exit('fix your .bashrc|.zshrc|.config/fish')
call(shell) # drops the user in a shell

I foolishly believed that $SHELL -c would've been enough for my purposes, but I recently discovered that the PATH check wasn't working on bash

I tried to look at some of the suggested solutions, but bash is much more brittle than i expected

Basically, I discovered that BASH_ENV, or manually sourcing with bash -c "source ~/.bashrc; do_your_stuff" won't work if some errors can be triggered by the sourced file

These same errors are not a problem if invoking bash interactively (that is, bash -i will ignore errors, and bash -c will silently fail). But bash -i is not a workable solution (I guess that this is because bash -i will "steal" the stdin, thus stopping the invoking python process)

The solution can't also simply be "don't use a broken ~/.bashrc":

  • The code has to be robust in face of poorly configured environments
  • This happens with a default .bashrc, like the one supplied by ubuntu (this is an old version)

The error actually happens (found it by set -xe before sourcing) inside /usr/share/bash-completion/bash_completion at this line: [[ -f /etc/slackware-version ]] && sysvdirs=( /etc/rc.d ) (how can this fail the process of sourcing is beyond me)

After lots of tweaking, I found out how could I detect such errors when sourcing:

set -e && . ~/.bashrc & wait %% ; echo $?

Weirdly, the following will fail instead:

set -e ; . ~/.bashrc & wait %% ; echo $?

Still, this isn't good enough, because neither

bash -c "set -e && . ~/.bashrc & wait %% ; echo \$?"
call(['bash', '-c', 'set -e && . ~/.bashrc & wait %% ; echo $?'])

will print a nonzero exit code. I'd also prefer to avoid to rely on shell specific code like this.

How can I make sure that when invoking bash -c the .bashrc will be correctly loaded?

As an alternative to this whole issue, I'm thinking of munging the .{bash,zsh}rc, to identify problems with the PATH. It's a poor solution, but it'd cover 90% of the cases, and avoid forking extra processes.

import re
r = '^export PATH\s?=\s?([^:]+:)+(\$PATH|\${PATH})'
g = re.match(r, 'export PATH=/usr/bin:$PATH').groups()
any(check_collision(x[:-1]) for x in g[:-1])

edit:

The bash -i flag seems to be the simplest and most promising half-solution, but its behavior is non obvious, some examples to help people understand:

This runs ls, and then subsequently leaves us in the Python prompt, with Python running

python3 -ic "import os; os.system(\"bash -c ls\")"

This stops the Python process

python3 -ic "import os; os.system(\"bash -ic ls\")"

Supplying a different stdin to the child process is not enough:

python3 -ic "import subprocess;f=open('/dev/null');subprocess.call(['bash', '-ic', 'ls'], stdin=f)"

Finally, bash -ic doesn't actually gets additional commands from the stdin, the following 2 commands have the exact same behavior:

 echo echo sed | bash -c "sed 's/[^ ]*$/bash/'"
 echo echo sed | bash -ic "sed 's/[^ ]*$/bash/'"
  • Wow, well found! Note that munging bashrc won't be enough either since i) the relevant lines might not start with export and, more importantly, ii) PATH can also be set in other files such as /etc/profile, ~/.profile, /etc/bash.bashrc, ~/.bash.login` etc. – terdon Mar 2 '15 at 16:04
  • Who is going to decide what commands are executed in this shell: the user, or your script? – Gilles Mar 4 '15 at 9:10
  • The user invokes the program, and the program (after doing the aforementioned changes to PATH and checks) leaves them in the shell, where they'll execute commands. The checks won't be executed in every shell/OS, and not all shells are POSIX (e.g. fish, powershell), so to try to keep the shell-invoking code as similar as possible to the existing code, the check is just done through the exit code of this program_that_checks_that_PATH_is_sane – berdario Mar 4 '15 at 11:14
-1

Don't use the SHELL environment variable in a script. That's the user's preferred interactive shell. You don't know anything about the syntax it supports. It could be tcsh, zsh, fish, rc, …

Don't load .bashrc from a script. The user is likely to put things that rely on bash being interactive and having a terminal; it might hang, or reconfigure your terminal, or hide your script's output, or any number of bad things. When you run bash -c …, .bashrc is not loaded, and that's by design.

If you need to check that PATH is sane, you can do it in Python, you don't need to invoke a shell for that. If by “the code has to be robust in face of poorly configured environments” you mean that your Python script should try to replicate the PATH of interactive shells even if it isn't started from one, then don't: you're far more likely to mess up in strange ways (and in particular to override an explicit setting from the user) than you're likely to help the user.

If your script is dropping the user into an interactive shell for some reason, then do run os.environ['SHELL'], falling back to pwd.getpwuid(os.geteuid()).pw_shell if the environment variable isn't set. This will run an interactive shell provided that your script is running in a terminal, you don't need to attempt to guess -i or other options which may or may not mean what you hope. If your script isn't running in a terminal then it has no business attempting to interact with the user and shouldn't start an interactive shell.

  • You might want to reread the question. Anyhow the pwd suggestion is interesting, I've never saw a unixlike system where $SHELL was not defined in an interactive user session, but it could be safer to keep it as fallback – berdario Mar 4 '15 at 2:52
  • @berdario You might want to clarify the question. What are you trying to achieve? Are you starting an interactive shell for the user to type in, are you starting a shell to run some commands, or what? – Gilles Mar 4 '15 at 9:07

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.