1

The user types 'sudo python helper.py' to run my file. I want the program to create an alias so the user only has to type 'helper'. So, when the python script runs I have it create an alias (after checking if the alias already exists).

lStr= 'alias helper=\'sudo python "'+os.path.abspath(__file__)+'"\''
print lStr
subprocess.Popen(lStr,shell=True)

But when the program exits, the alias still doesn't exist and I don't know why not. If I manually copy the result of the print statement into bash, then, the alias is created properly. It doesn't work when it is called from within the python program though. Any ideas?


Because my program has a substantial amount of interaction, I can't use the answer given at Use python to dynamically create bash aliases (eval "$(python ...)"), because that would be capturing menu text and other content involved in user interaction.

I can (and do) update ~/.bash_aliases, so users already get new aliases the next time they open a shell -- but is there a way to have more immediate effect even when a program interacts with the user?

Community
  • 1
  • 1
Vexen Crabtree
  • 339
  • 3
  • 16
  • 1
    This is the same as running `sh -c 'alias helper="something"'` in bash: it's starting a **whole new shell**, and running your alias command in that new shell, and then letting the new shell *exit* since it's done running its script. Shell has exited -> no more alias. – Charles Duffy May 10 '17 at 21:13
  • It's the same underlying problem as [change directory with python](http://stackoverflow.com/questions/13732221/change-directory-with-python) or [set bash variable from python script](http://stackoverflow.com/questions/26767552/set-bash-variable-from-python-script). – Charles Duffy May 10 '17 at 21:15
  • Yep, got it. I don't do the "alias" command in the right context. So, the shell that I create in the Popen cmd disappears, along with the alias. – Vexen Crabtree May 10 '17 at 21:21
  • 1
    Yup. The sticky thing is that by design, you can't cause your parent process to run code of your choice, except with that parent process's willing participation (such as `eval`). That said, `eval` -- since it runs data as code -- has significant potential security impact, hence some of my comments on the answer, and [BashFAQ #48](http://mywiki.wooledge.org/BashFAQ/048). – Charles Duffy May 10 '17 at 21:22
  • @CharlesDuffy, you've flagged this as a duplicate of another, but, the accepted solution for that one doesn't work here, for two reasons (which I'll add to my question): (1) There is no way I'm going to get my users to type in the eval statement - it's hard enough getting them to type "sudo python" in front of the filename). (2) My program already prints a lot of things to stdout (multiple screens, after some Q&A sessions with the user). Evalling the whole lot won't work. – Vexen Crabtree May 10 '17 at 21:26
  • Ping me again when you're done with those distinguishing edits. – Charles Duffy May 10 '17 at 21:27
  • If anyone wants to add an answer of "Not possible, because of the context" (etc), I'll mark that as the correct answer. The user just won't have the alias available during their first shell after running the helper script. – Vexen Crabtree May 10 '17 at 21:53
  • re: "can't eval the output of print statements" -- content intended for the human rather than programmatic consumption should be on stderr, not stdout. – Charles Duffy May 10 '17 at 21:55
  • Isn't stderr for errors? So, if I change all my print statements to print to stderr (hopefully there's a way to change their default output method without having to edit each one...), then, I could eval just the output that went to stdout, even though both appear in the term session as text? – Vexen Crabtree May 10 '17 at 21:59
  • Exactly, command substitutions (`$(...)`) capture only stdout. stderr is for status and logs generally -- basically, everything except for *output*. – Charles Duffy May 10 '17 at 22:01
  • Alright, good information and thanks for the quick replies. Although I'm not going down this route (because of complications I've not mentioned!), it is still correct-answer-worthy! – Vexen Crabtree May 10 '17 at 22:04
  • If you want to add the "change all the printing to stderr, and eval() the results of stdout" as answer I'll mark it as the correct one. Didn't notice this conversation ^^^ was all in the comments to the OP and not on an answer. – Vexen Crabtree May 10 '17 at 22:05

3 Answers3

2

When you use subprocess, you are doing just that, opening another process to run the target command.

The problem is that you are creating a new shell environment, setting the alias, then immediately exiting the new shell, losing the configuration.

What you could do, is have the Python process simply print the commands to be evaluated, then use eval to load them into your current environment.

Python:

lStr= 'alias helper=\'sudo python "'+os.path.abspath(__file__)+'"\''
print lStr

Bash, evaluate the output, and run the alias commands in the current context.

eval "$( python ./myScript )"

And then

alias helper
alias helper='sudo python "/Users/xyz/test.py"'
Matt Clark
  • 27,671
  • 19
  • 68
  • 123
  • Quotes. `eval "$(python ./myScript)"` -- the impact of doing otherwise varies exactly on how the arguments get string-split and expanded, but as a general rule it's never an improvement. – Charles Duffy May 10 '17 at 21:13
  • I'd also strongly suggest using `pipes.quote()` (or its Python 3 replacement, `shlex.quote()`). Otherwise, behavior if your Python script has an interesting name (maybe it's in a directory name that contains literal quote characters, or a `$()` expansion? Those **are** valid on typical UNIX filesystems) will be... *surprising*. – Charles Duffy May 10 '17 at 21:17
  • ...so, using `pipes.quote()` for both stages, making sure that your generated shell command is entirely safe in both steps, would look like: `inner_cmd='sudo python %s' % (pipes.quote(os.path.abspath(__file__)),); outer_cmd='alias helper=%s' % (pipes.quote(inner_cmd)); print(outer_cmd)` – Charles Duffy May 10 '17 at 21:20
  • Thanks for the ideas Matt, but my program prints a lot to the screen (menus, information, alers, all sorts) before it exits, so using eval on the results won't work (I think). – Vexen Crabtree May 10 '17 at 21:34
  • @CharlesDuffy thank you for the input, these should be comments on the original question - I am just giving the OP a suggestion for how to evaluate commands from his python scrip in his current environment. I have not changed the code OP is using to generate said commands. – Matt Clark May 11 '17 at 17:01
1

You can perform your user interaction either on stderr, or communicating directly with /dev/tty, to keep stdout free for output to be eval'd.

That is to say:

eval "$(python yourprogram)"

won't capture lines like:

print >>sys.stderr, "hello"

or:

tty = open('/dev/tty', 'w')
print >>tty, "hello"
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
0

I am adding a second answer - my first one is still technically valid, as it answers the first question you asked. This one is here to serve now that the question has changed entirely.

While the accepted answer will work, your user will need to run the helper script every time they open up a new shell, as the aliases will not be persisted between sessions.

What you should do, is use your helper script to generate a standard environment file, a file that is simply your current stdout, and dump it into a file on disk.

You should then direct your user to this file, and have them add a source line to either their .bashrc or .profile

By doing this, every time a user starts a new terminal, the environment will be loaded into their session.

Ex:

lStr= 'alias helper=\'sudo python "'+os.path.abspath(__file__)+'"\''

fPath = '/path/to/envFile'

f = open(fPath, 'w')
f.write(lStr)
f.close()

print "Hi."
print "Append this to your ~/.bashrc"
print "  source " + fPath

Results in

Hi.
Append this to your ~/.bashrc
  source /path/to/envFile

This also gives your user a chance to see what you are doing before blindly running the commands.

Community
  • 1
  • 1
Matt Clark
  • 27,671
  • 19
  • 68
  • 123
  • I still have the same objection here I had to the last one -- you're assuming without justification that `os.path.abspath(__file__)` is a string that evals to itself inside double quotes, but a filename can *contain* double quotes, or `$`s, or any amount of other content that's syntactically active in shell. Python provides the tools to use to safely escape content to be evaluated by a shell; why *not* use them? – Charles Duffy May 11 '17 at 16:47
  • @CharlesDuffy thank you for the input, these should be comments on the original question - I am just giving the OP a suggestion for how to evaluate commands from his python scrip in his current environment. I have not changed the code OP is using to generate said commands. – Matt Clark May 11 '17 at 17:01