13

In Fabric, when I try to use any alias' or functions from my .bash_profile file, they are not recognized. For instance my .bash_profile contains alias c='workon django-canada', so when I type c in iTerm or Terminal, workon django-canada is executed.

My fabfile.py contains

def test():
    local('c')

But when I try fab test it throws this at me: [localhost] local: c

/bin/sh: c: command not found

Fatal error: local() encountered an error (return code 127) while executing 'c'

Aborting.

Other Fabric functions work fine. Do I have to specify my bash profile somewhere in fabric?

saul.shanabrook
  • 3,068
  • 3
  • 31
  • 49

3 Answers3

21

EDIT - As it turns out, this was fixed in Fabric 1.4.4. From the changelog:

[Feature] #725: Updated local to allow override of which local shell is used. Thanks to Mustafa Khattab.

So the original question would be fixed like this:

def test():
    local('c', shell='/bin/bash')

I've left my original answer below, which only relates to Fabric version < 1.4.4.


Because local doesn't use bash. You can see it clearly in your output

/bin/sh: c: command not found

See? It's using /bin/sh instead of /bin/bash. This is because Fabric's local command behaves a little differently internally than run. The local command is essentially a wrapper around the subprocess.Popen python class.

http://docs.python.org/library/subprocess.html#popen-constuctor

And here's your problem. Popen defaults to /bin/sh. It's possible to specify a different shell if you are calling the Popen constructor yourself, but you're using it through Fabric. And unfortunately for you, Fabric gives you no means to pass in a shell, like /bin/bash.

Sorry that doesn't offer you a solution, but it should answer your question.

EDIT

Here is the code in question, pulled directly from fabric's local function defined in the operations.py file:

p = subprocess.Popen(cmd_arg, shell=True, stdout=out_stream,
    stderr=err_stream)
(stdout, stderr) = p.communicate()

As you can see, it does NOT pass in anything for the executable keyword. This causes it to use the default, which is /bin/sh. If it used bash, it'd look like this:

p = subprocess.Popen(cmd_arg, shell=True, stdout=out_stream,
    stderr=err_stream, executable="/bin/bash")
(stdout, stderr) = p.communicate()

But it doesn't. Which is why they say the following in the documentation for local:

local is simply a convenience wrapper around the use of the builtin Python subprocess module with shell=True activated. If you need to do anything special, consider using the subprocess module directly.

Eric Palakovich Carr
  • 22,701
  • 8
  • 49
  • 54
  • 1
    But the [Fabric docs](http://docs.fabfile.org/en/0.9.0/usage/env.html#shell) say: "shell Default: **/bin/bash** -l -c Value used as shell wrapper when executing commands with e.g. run. Must be able to exist in the form "" – e.g. the default uses Bash’s -c option which takes a command string as its value." – saul.shanabrook Nov 18 '11 at 00:02
  • 1
    Yeah, the docs are referring to the commands run and sudo. The local command works different than those behind the scenes. I've edited the answer to show the code in question. – Eric Palakovich Carr Dec 20 '11 at 15:00
  • 1
    From version 1.6, Fabric allows to use shell you want. `local('source {venv} && pelican {content_path} -s pelicanconf.py -t {theme} && deactivate'.format(**env), shell='/bin/bash')` There is 'shell' option for that. – Aleksandr Zonov Jul 22 '13 at 05:40
  • 1
    @anzo The `shell` option in `local` doesn't allow you to specify flags (such as `bash -l -c "local command"` which would be needed to access an alias in the .bashrc . It works by default on `run` or `sudo`. – Dave Aug 23 '13 at 07:55
3

One workaround is simply to wrap whatever command you have around a bash command:

@task
def do_something_local():
    local("/bin/bash -l -c 'run my command'")

If you need to do a lot of these, consider creating a custom context manager.

Community
  • 1
  • 1
ashwoods
  • 2,209
  • 1
  • 22
  • 36
0

It looks like you're trying to use virtualenvwrapper locally. You'll need to make your local command string look like this:

    local("/bin/bash -l -c 'workon django-canada && python manage.py runserver'")

Here's an example by yours truly that does that for you in a context manager.

Community
  • 1
  • 1
Dave
  • 2,396
  • 2
  • 22
  • 25