Skip to content

Instantly share code, notes, and snippets.

@alixedi
Last active July 7, 2016 13:18
Show Gist options
  • Save alixedi/af585194437d54493e65 to your computer and use it in GitHub Desktop.
Save alixedi/af585194437d54493e65 to your computer and use it in GitHub Desktop.
Python tool to make long CLI commands interactive
#! /usr/bin/env python
'''
Nutshell - make long CLI commands interactive
---------------------------------------------
I am lazy. I am forgetful. I often have to write long CLI commands.
The shell history does an awesome job of remembering them for me but too often
I end up working too hard in trying to find the line I wrote a couple of days
back and having to change something simlple e.g. name of a file etc.
With Nutshell I am trying to make a sidekick for alias. At its simplest:
$ python nutshell.py 'hadoop fs -cat hdfs:///#project/#package/#file | head -n#lines'
'project' > my-project
'package' > my-package
'file' > data.csv
'lines' > 5
<Runs hadoop fs -cat hdfs:///my-project/my-package/data.csv | head -n5>
...
You can set environment variables starting with `NUTSHELL_` to pass in arguments like so:
$ export NUTSHELL_lines=10
$ python nutshell.py 'hadoop fs -cat hdfs:///#project/#package/#file | head -n#lines'
'project' > my-project
'package' > my-package
'file' > data.csv
<Runs hadoop fs -cat hdfs:///my-project/my-package/data.csv | head -n10>
...
You can also pass in CLI arguments - useful for having your shell history just so:
$ export NUTSHELL_lines=10
$ python nutshell.py 'hadoop fs -cat hdfs:///#project/#package/#file | head -n#lines' #file='foo.csv'
'project' > my-project
'package' > my-package
<Runs hadoop fs -cat hdfs:///my-project/my-package/foo.csv | head -n10>
...
Finally, CLI arguments override environment variables:
$ export NUTSHELL_lines=10
$ python nutshell.py 'hadoop fs -cat hdfs:///#project/#package/#file | head -n#lines' #lines='8'
'project' > my-project
'package' > my-package
'file' > data.csv
<Runs hadoop fs -cat hdfs:///my-project/my-package/foo.csv | head -n8>
...
Enjoy!
'''
import os
import sys
from subprocess import Popen, PIPE, STDOUT
from string import Template
def _stdin23(prompt):
'''Takes input from user. Works for Python 2 and 3.'''
_v = sys.version[0]
return input(prompt) if _v is '3' else raw_input(prompt)
def env(env=None, prefix='NUTSHELL_'):
'''Reads env variables like NUTSHELL_<var_name>, parse
them and returns a dict.
>>> env_overrides(env={'yo': 0, 'A_yo': 1}, pre='A_')
{'yo': 1}
'''
env = env or os.environ
_vars = env.iteritems()
for k, v in env.iteritems():
if k.startswith(prefix):
yield (k.lstrip(prefix), v)
def cli(args=None, prefix='#'):
'''Reads cli overrides like #<var_name>=<value>.'''
args = args or sys.argv[1:]
for arg in args:
if arg.startswith(prefix):
k, v = arg.split('=')
yield (k.lstrip(prefix), v)
class Nutshell(object):
def __init__(self, args, prefix='#', _input=_stdin23):
self.cmd = args[0]
self.overrides = args[1:]
self.prefix = prefix
self._input = _input
self.tpl_class = type("NutTpl", (Template,), {'delimiter': '#'})
def render(self, overrides):
'''Tries to render template, fails by asking questions
if no overrides found.'''
tpl = self.tpl_class(self.cmd)
data = overrides or {}
while True:
try:
return tpl.substitute(**data)
except KeyError as e:
ans = str(self._input('%s > ' % e))
data.update({e.message: ans})
def run(self):
overrides = dict(list(env()) + list(cli()))
cmd = self.render(overrides)
print cmd
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT)
p.wait()
return p.stdout.read().strip()
if __name__ == '__main__':
print Nutshell(sys.argv[1:]).run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment