I'd like to control some bash programs (local, or ssh) with some (eventually interactive) scripts (say python) in a robust way. I'd like to send commands, get the stdout/stderr of that command at the end of the execution, as well as the exit code of the command. Note that the bash commands are chosen by an external user in real time, so it could be anything.
To get an idea of what I want, I'd like to provide a function (the programming language does not really matter):
def send_command(command):
# how to implement ????
return (stdin_output, stderr_output, exit_code)
so that I could run:
(o,e,ex) = send_command("pwd")
print("The output of your last command is ", o)
(o,e,ex) = send_command("cd /tmp")
(o,e,ex) = send_command("pwd")
print("The output of your last command is ", o)
that would produce:
The output of your last command is /home/me/
The output of your last command is /tmp/
Moreover, I want to make sure that no command
could "mess-up" my python script, and make it believe that the command is finished when it's not. For example, I don't want to let him record all the next commands, so the following code should block on the first line forever (the first command is an infinite loop) and never call the second line:
send_command("echo $PS1; <malicious command that loops forever and sends all the next inputs to a webserver>")
send_command("echo mysecretpassword") # This line should never be executed
What I tried:
One option would be to run a new bash instance every-time, and run a script in this console. But this has two drawbacks: Firstly it's not super efficient as every-time it will load bash/ssh from scratch. Secondly the state of the bash (environment, working directory) is a new one at every run. Eg: if I run "cd /tmp", on the next command I'll be back at home.
The other option I see is expect
/pexpect
. The problem of this solution is that as far as I understand it, it is just a parsing of the output. And this parsing loses the semantic of the bash output. For example, if I expect $
, I cannot make sure the command is finished, and it could be the command itself that wrote this text. Likewise if the programs are not fully trusted it could be even worse: a program could fake to be a bash, and read all the bash commands. One may try to setup a complicated PS1, but this environment variable is forwarded to the commands, so any program that output this variable like "env" would break this command, so it's not really a robust solution. Finally, I don't see a "good" way to get the exit code of the command, better than running a bash command that outputs the code of the last typed command, and parse this result.
Is there a third option I missed?
Thanks!
-- EDIT --
I found the environment variable PROMPT_COMMAND
, that is not transmitted in the environment variables as far as I can see. But it's still possible to read it directly from the shell with a simple echo
so it is not super robust either...
$ PS1=""
PROMPT_COMMAND='echo -n "Prompt MYSECRETNUMBERSETBYEXPECT (last exit $?) "'
Prompt MYSECRETNUMBERSETBYEXPECT (last exit 0) ls /tmp/o
file.txt
Prompt MYSECRETNUMBERSETBYEXPECT (last exit 0) rm /tmp/a/donotexist
rm: impossible de supprimer '/tmp/a/donotexist': No such file or directory
Prompt MYSECRETNUMBERSETBYEXPECT (last exit 1) env | grep SECRET
Prompt MYSECRETNUMBERSETBYEXPECT (last exit 1)
-- EDIT 2 --
Another solution that seems more robust would be to use pexpect
with a random secret prompt, and then embrace any command
with:
PS1=""; <command>; PS1="My secret PS1 61547851396512fdsqjklqdf (last code $?) >>$ "
(this code just removes/recreates the PS1 secret prompt before/after the command to make sure the command does not have access to the prompt value). One can even redirect the output to a file if needed without creating a subshell by replacing <command>
with { <command>; } > /tmp/out.stdout 2> /tmp/out.stderr
(do not forget the spaces and semicolon). Is it the best way to proceed?
os.chdir(path)
).