Python subprocess.call a bash alias
At work there's a script that lists completed tasks. This was written by someone else and is hosted over the network. I have an alias in my .bashrc that calls this script, with its many flags and such, and I wanted to write a python script that would call this alias every few minutes so I can have a shell open with updated stats. However, subprocess.call("myAlias")
fails. I'm still fairly new to python, and am struggling to figure this out.
from subprocess import call
def callAlias():
call("myAlias")
callAlias()
I plan on adding more too it, but I hit the snag on my first step. :P
I would post more, but there's a lot of sensitive confidential stuff I have to be careful with. Sorry for the vague code, and lack of error output.
Update: Thanks for the upvotes for this hack to workaround the problem, I'm glad it's useful. But a much better answer is tripleee's, languishing far down the page...
If the alias you require is defined in ~/.bashrc, then it won't get run for a few reasons:
1) You must give the 'shell' keyword arg:
subprocess.call('command', shell=True)
Otherwise your given command is used to find an executable file, rather than passed to a shell, and it is the shell which expands things like aliases and functions.
2) By default, subprocess.call and friends use the '/bin/sh' shell. If this is a Bash alias you want to invoke, you'll need to tell subprocess to use bash instead of sh, using the 'executable' keyword arg:
subprocess.call('command', shell=True, executable='/bin/bash')
3) However, /bin/bash will not source ~/.bashrc unless started as an 'interactive' shell (with '-i'.) Unfortunately, you can't pass executable='/bin/bash -i', as it thinks the whole value is the name of an executable. So if your alias is defined in the user's normal interactive startup, e.g. in .bashrc, then you'll have to invoke the command using this alternative form:
subprocess.call(['/bin/bash', '-i', '-c', command])
# i.e. shell=False (the default)
You need to set the shell
keyword to True:
call("myAlias", shell=True)
From the relevant documentation:
If
shell
isTrue
, the specified command will be executed through the shell. This can be useful if you are using Python primarily for the enhanced control flow it offers over most system shells and still want access to other shell features such as filename wildcards, shell pipes and environment variable expansion.
Aliases are a shell feature (e.g. they are defined and interpreted by the shell).
However, the shell (/bin/sh) is executed non-interactively, so no .profile
or .bashrc
files are read and your alias probably is not going to be available.
If you are reluctant to use the full expanded command into your python script, you'll have to use the $ENV
environment variable to make the shell read the file with the alias defined in it anyway:
call("myAlias", shell=True, env=dict(ENV='/path/to/aliasfile'))
The recommended solution is to not use an alias to define functionality which isn't exclusively for interactive use (and even then, shell functions are superior in a number of ways).
Refactor the alias into a standalone script and call it like any other external command.
In more detail, if you have
alias myalias='for x in foo bar baz; do
frobnicate "$x"; done'
you can improve it so it doesn't pollute your global namespace by turning it into a function
myalias () {
local x
for x in foo bar baz; do
frobnicate "$x"
done
}
or just save it as /usr/local/bin/myalias
and chmod a+x /usr/local/bin/myalias
to make it executable for everyone;
#!/bin/sh
for x in foo bar baz; do
frobnicate "$x"
done
(This runs in a subprocess so x
will be gone when the script finishes; so we don't need to make it local
.)
(Of course, if frobnicate
is at all competently written, maybe you can simplify to just frobnicate foo bar baz
as well.)
This is a common FAQ.
I modified Jonathan's reason #2 slightly to make this work. Make a /usr/local/bin/interactive_bash
file that contains
#!/bin/bash
/bin/bash -i "$@"
and chmod +x
it. Then from Python you can call
subprocess.call('my_alias', shell=True, executable='/usr/local/bin/interactive_bash')