Search code examples
pythonbashshellsubprocessalias

Python unexpected non-zero returncode when checking alias command set in .bashrc file


I want to call an alias command set in a .bashrc file from python. However, the returncode of source ~/.bashrc; command -v command is for some reason 1 instead of 0 which is the case if the same command is executed directly in the shell. It should be noted that with a default command like echo instead of the alias command, python correclty returns 0 as the returncode.

To demonstrate the issue, I created a minimal example, where I use a regular shell script define_alias.sh instead of the .bashrc file:

test_bash_command.py:

import os
import subprocess

path_home = os.path.expanduser('~')
proc = subprocess.Popen(['/bin/bash', '-c', 'source %s/define_alias.sh; command -v testcommand' % path_home], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

proc_stdout, proc_stderr = proc.communicate()
proc_returncode = proc.returncode

print("Returncode: %s" % proc_returncode)
print("Stdout: %s" % proc_stdout)
print("Stderr: %s" % proc_stderr)

define_alias.sh:

# .bashrc
# imagine this is a .bashrc file

echo "I'm in the shell script"
alias testcommand='echo THIS IS A TEST'

Output:

wssadmin /global/wss/fs01/fk_bpr/waif_v8
$ python test_bash_command.py
Returncode: 1
Stdout: b"I'm in the shell script\n"
Stderr: b''

wssadmin /global/wss/fs01/fk_bpr/waif_v8
$ env -i bash --norc --noprofile
bash-4.4$ bash -c "source /home/wssadmin/define_alias.sh; command -v testcommand; echo $?"
I'm in the shell script
0
bash-4.4$ source /home/wssadmin/define_alias.sh; command -v testcommand; echo $?
I'm in the shell script
alias testcommand='echo THIS IS A TEST'
0
bash-4.4$ exit
exit

wssadmin /global/wss/fs01/fk_bpr/waif_v8
$ source /home/wssadmin/define_alias.sh; command -v testcommand; echo $?
I'm in the shell script
alias testcommand='echo THIS IS A TEST'
0

Question: Why is the returncode given to python not 0 like in the bash case? How can I get a returncode of 0 for the given command and additionally retrieve the stdout and stderr of the command -v testcommand (instead of only having that of the echo command)?

The source command itself is executed, which can be seen in the printed stdout variable. Please do note that I would not like to change the /bin/bash -c nor the shell=False parameter in subprocess.Popen as otherwise I would get issues with other commands called in other python scripts by a run_bash_cmd function wrapped around the subprocess.Popen.

Your help is much appreciated!


Solution

  • You need to run shopt -s expand_aliases to enable alias support, which is turned off by default in noninteractive shells. (Historically, aliases were part of the optional interactive extensions in the User Portability Utilities annex to the POSIX sh standard; it's only as of the Issue 7 revision to the POSIX standard that they're part of the proper base featureset).

    >>> subprocess.run(['bash', '-c', 'alias foo=true;'
    ...                 'command -v foo']).returncode
    1
    >>> subprocess.run(['bash', '-c', 'alias foo=true;'
    ...                 'shopt -s expand_aliases; command -v foo']).returncode
    alias foo='true'
    0
    

    If you want to actually run that alias, however, it needs to be defined (and alias expansion needs to be enabled) before parse time. To make simple statements be parsed individually, make sure they aren't part of a group or other compound statement, and separate them with newlines rather than ;s or &s.

    >>> subprocess.run(['bash', '-c', '\n'.join(['alias foo=true', 'foo'])],
    ...                env=(os.environ | {'BASHOPTS': 'expand_aliases'}))
    CompletedProcess(args=['bash', '-c', 'alias foo=true\nfoo'], returncode=0)