Ever start a bash script, then wish you’d started it in python?
Use this Docker image to convert your script.
IntroductionI routinely use both bash and python to quickly whip up tools for short and long-term uses.
Generally I start with a bash script because it’s so fast to get going, but as time goes on I add features, and then wish I had started it in python so that I
I found a bash2py tool, which looked good, but came as a zipped source download (not even in a git repo!).
I created a Docker image to convert it, and have used it a couple of times. With a little bit of effort you can quickly convert your bash script to a python one and move ahead.
ExampleI’m going to use an artificially simple but realistic bash script to walk through a conversion process.
Let’s say I’ve written this bash script to count the number of lines in a list of files, but want to expand this to do very tricky things based on the output:
#!/bin/bash if [ $# -lt 1 ] then echo "Usage: $0 file ..." exit 1 fi echo "$0 counts the lines of code" l=0 for f in $* do l=`wc -l $f | sed 's/^\([0-9]*\).*$/\1/'` echo "$f: $l" doneHere’s a conversion session:
imiell@Ians-Air:/space/git/work/bin$ docker run -ti imiell/bash2py Unable to find image 'imiell/bash2py:latest' locally latest: Pulling from imiell/bash2py 357ea8c3d80b: Already exists 98b473a7fa6a: Pull complete a7f8553161b4: Pull complete a1dc4858a149: Pull complete 752a5d408084: Pull complete cf7fa7bc103f: Pull complete Digest: sha256:110450838816d2838267c394bcc99ae00c99f8162fa85a1daa012cff11c9c6c2 Status: Downloaded newer image for imiell/bash2py:latest root@89e57c8c3098:/opt/bash2py-3.5# vi a.sh root@89e57c8c3098:/opt/bash2py-3.5# ./bash2py a.sh root@89e57c8c3098:/opt/bash2py-3.5# python a.sh.py Usage: a.sh.py file ... root@89e57c8c3098:/opt/bash2py-3.5# python a.sh.py a.sh a.sh.py counts the lines of code a.sh: 16So that’s nice, I now have a working python script I can continue to build on!
SimplifyBefore you get too excited, unfortunately it’s not magically working out which python modules to import and cleverly converting everything from bash to python. However, what’s convenient about this is that you can adjust the script where you care about it, and build from there.
To work through this example, here is the raw conversion:
#! /usr/bin/env python from __future__ import print_function import sys,os class Bash2Py(object): __slots__ = ["val"] def __init__(self, value=''): self.val = value def setValue(self, value=None): self.val = value return value def GetVariable(name, local=locals()): if name in local: return local[name] if name in globals(): return globals()[name] return None def Make(name, local=locals()): ret = GetVariable(name, local) if ret is None: ret = Bash2Py(0) globals()[name] = ret return ret def Array(value): if isinstance(value, list): return value if isinstance(value, basestring): return value.strip().split(' ') return [ value ] class Expand(object): @staticmethod def at(): if (len(sys.argv) < 2): return [] return sys.argv[1:] @staticmethod def star(in_quotes): if (in_quotes): if (len(sys.argv) < 2): return "" return " ".join(sys.argv[1:]) return Expand.at() @staticmethod def hash(): return len(sys.argv)-1 if (Expand.hash() < 1 ): print("Usage: "+__file__+" file ...") exit(1) print(__file__+" counts the lines of code") l=Bash2Py(0) for Make("f").val in Expand.star(0): Make("l").setValue(os.popen("wc -l "+str(f.val)+" | sed \"s/^\\([0-9]*\\).*$/\\1/\"").read().rstrip("\n")) print(str(f.val)+": "+str(l.val))The guts of the code is in the for loop at the bottom.
bash2py does some safe conversion and wrapping of the bash script into some methods such as ‘Make’, ‘Array’ et al that we can get rid of with a little work.
By replacing:
Bash2Py(0) with 0 Make(“f”).val with f and Make(“l”) with l etc f.val with f and l.val with l etc 54,57c27,30 < l=Bash2Py(0) < for Make("f").val in Expand.star(0): < Make("l").setValue(os.popen("wc -l "+str(f.val)+" | sed \"s/^\\([0-9]*\\).*$/\\1/\"").read().rstrip("\n")) < print(str(f.val)+": "+str(l.val)) --- > l=0 > for f in Expand.star(0): > l = os.popen("wc -l "+str(f)+" | sed \"s/^\\([0-9]*\\).*$/\\1/\"").read().rstrip("\n") > print(str(f)+": "+str(l))I simplify that section.
I can remove the now-unused methods to end up with the simpler:
#! /usr/bin/env python from __future__ import print_function import sys,os class Expand(object): @staticmethod def at(): if (len(sys.argv) < 2): return [] return sys.argv[1:] @staticmethod def star(in_quotes): if (in_quotes): if (len(sys.argv) < 2): return "" return " ".join(sys.argv[1:]) return Expand.at() @staticmethod def hash(): return len(sys.argv)-1 if (Expand.hash() < 1 ): print("Usage: "+__file__+" file ...") exit(1)