How to answer to prompts automatically with python fabric?
Question:
I want to run a command which prompts me to enter yes/no or y/n or whatever. If I just run the command local("my_command")
then it stops and asks me for input. When I type what is needed, script continues to work. How can I automatically respond to the prompt?
Answers:
Note: this answer is several years old, and in the mean time fabric has (interestingly similar looking) implementation of this. See the answer by @timothée-jeannin below.
See https://stackoverflow.com/a/10007635/708221
pip install fexpect
from ilogue.fexpect import expect, expecting, run
prompts = []
prompts += expect('What is your name?','John')
prompts += expect('Are you at stackoverflow?','Yes')
with expecting(prompts):
run('my_command')
Fexpect adds answering to prompts to fabric with use of pexpect
I have used simple echo pipes to answer prompts with Fabric.
run('echo "yesn"| my_command')
Starting from version 1.9
, Fabric includes a way of managing this properly.
The section about Prompts in the Fabric documentation says:
The prompts dictionary allows users to control interactive prompts. If
a key in the dictionary is found in a command’s standard output
stream, Fabric will automatically answer with the corresponding
dictionary value.
You should be able to make Fabric automatically answer prompts like this:
with settings(prompts={'Do you want to continue [Y/n]? ': 'Y'}):
run('apt-get update')
run('apt-get upgrade')
Putting this as an answer though its a comment from @BobNadler
run(“yes | my_command”);
To expand a bit on Timothée’s excellent answer, here’s the code that Fabric uses when checking the prompts
dictionary.
def _get_prompt_response(self):
"""
Iterate through the request prompts dict and return the response and
original request if we find a match
"""
for tup in env.prompts.iteritems():
if _endswith(self.capture, tup[0]):
return tup
return None, None
Fabric uses .endswith
for its check, so make sure you include trailing spaces in the string you use as a key in the prompts
dictionary.
For example – let’s say you are trying to automate the Django test database prompt
Type ‘yes’ if you would like to try deleting the test database ‘test_my_app’, or ‘no’ to cancel:
All we need is enough of the end of the prompt so that it is unique. Include trailing spaces.
django_test_database_prompt = "or 'no' to cancel: "
# won't work without this trailing space ^
with settings(
prompts={django_test_database_prompt : 'yes'}
):
run('%s %s' % (virtualenv_python_path,
test_runner_file_path,
)
)
In Fabric 2.1, this can be accomplished using the auto-respond example that is available through the invoke package (a dependency of Fabric 2.1):
>>> from invoke import Responder
>>> from fabric import Connection
>>> c = Connection('host')
>>> sudopass = Responder(
... pattern=r'[sudo] password:',
... response='mypasswordn',
... )
>>> c.run('sudo whoami', pty=True, watchers=[sudopass])
[sudo] password:
root
<Result cmd='sudo whoami' exited=0>
Note that this is not limited to sudo passwords and can be used anywhere where you have a pattern to match for and a canned response (that may not be a password).
There are a couple of tips:
pty=True
is NOT necessary but could be important because it makes the flow seem more realistic. e.g. if you had a prompt expecting a yes/no answer to proceed, without it(pty=True
) your command would still run; except, your choice/input(specified by response
) won’t be shown as typed as the answer as one might expect
- The
pattern
specified within the Responder
can often include spaces at the end of the line so try adding spaces when the watcher
doesn’t seem to match.
-
According to the note discussed at the end of the watcher docs:
The pattern argument to Responder is treated as a regular expression, requiring more care (note how we had to escape our square-brackets in the above example) but providing more power as well.
So, don’t forget to escape (using backslashes) where necessary.
I want to run a command which prompts me to enter yes/no or y/n or whatever. If I just run the command local("my_command")
then it stops and asks me for input. When I type what is needed, script continues to work. How can I automatically respond to the prompt?
Note: this answer is several years old, and in the mean time fabric has (interestingly similar looking) implementation of this. See the answer by @timothée-jeannin below.
See https://stackoverflow.com/a/10007635/708221
pip install fexpect
from ilogue.fexpect import expect, expecting, run
prompts = []
prompts += expect('What is your name?','John')
prompts += expect('Are you at stackoverflow?','Yes')
with expecting(prompts):
run('my_command')
Fexpect adds answering to prompts to fabric with use of pexpect
I have used simple echo pipes to answer prompts with Fabric.
run('echo "yesn"| my_command')
Starting from version 1.9
, Fabric includes a way of managing this properly.
The section about Prompts in the Fabric documentation says:
The prompts dictionary allows users to control interactive prompts. If
a key in the dictionary is found in a command’s standard output
stream, Fabric will automatically answer with the corresponding
dictionary value.
You should be able to make Fabric automatically answer prompts like this:
with settings(prompts={'Do you want to continue [Y/n]? ': 'Y'}):
run('apt-get update')
run('apt-get upgrade')
Putting this as an answer though its a comment from @BobNadler
run(“yes | my_command”);
To expand a bit on Timothée’s excellent answer, here’s the code that Fabric uses when checking the prompts
dictionary.
def _get_prompt_response(self):
"""
Iterate through the request prompts dict and return the response and
original request if we find a match
"""
for tup in env.prompts.iteritems():
if _endswith(self.capture, tup[0]):
return tup
return None, None
Fabric uses .endswith
for its check, so make sure you include trailing spaces in the string you use as a key in the prompts
dictionary.
For example – let’s say you are trying to automate the Django test database prompt
Type ‘yes’ if you would like to try deleting the test database ‘test_my_app’, or ‘no’ to cancel:
All we need is enough of the end of the prompt so that it is unique. Include trailing spaces.
django_test_database_prompt = "or 'no' to cancel: "
# won't work without this trailing space ^
with settings(
prompts={django_test_database_prompt : 'yes'}
):
run('%s %s' % (virtualenv_python_path,
test_runner_file_path,
)
)
In Fabric 2.1, this can be accomplished using the auto-respond example that is available through the invoke package (a dependency of Fabric 2.1):
>>> from invoke import Responder
>>> from fabric import Connection
>>> c = Connection('host')
>>> sudopass = Responder(
... pattern=r'[sudo] password:',
... response='mypasswordn',
... )
>>> c.run('sudo whoami', pty=True, watchers=[sudopass])
[sudo] password:
root
<Result cmd='sudo whoami' exited=0>
Note that this is not limited to sudo passwords and can be used anywhere where you have a pattern to match for and a canned response (that may not be a password).
There are a couple of tips:
pty=True
is NOT necessary but could be important because it makes the flow seem more realistic. e.g. if you had a prompt expecting a yes/no answer to proceed, without it(pty=True
) your command would still run; except, your choice/input(specified byresponse
) won’t be shown as typed as the answer as one might expect- The
pattern
specified within theResponder
can often include spaces at the end of the line so try adding spaces when thewatcher
doesn’t seem to match. -
According to the note discussed at the end of the watcher docs:
The pattern argument to Responder is treated as a regular expression, requiring more care (note how we had to escape our square-brackets in the above example) but providing more power as well.
So, don’t forget to escape (using backslashes) where necessary.