Skip to content

Instantly share code, notes, and snippets.

@mmurdoch
Forked from pudquick/shellista.py
Created September 4, 2013 20:40
Show Gist options
  • Select an option

  • Save mmurdoch/6442567 to your computer and use it in GitHub Desktop.

Select an option

Save mmurdoch/6442567 to your computer and use it in GitHub Desktop.

Revisions

  1. @pudquick pudquick revised this gist Sep 3, 2013. 1 changed file with 28 additions and 0 deletions.
    28 changes: 28 additions & 0 deletions shellista.py
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,33 @@
    import os, cmd, sys, re, glob, os.path, shutil, zipfile, tarfile, gzip

    # Credits
    #
    # The python code here was written by pudquick@github
    #
    # License
    #
    # This code is released under a standard MIT license.
    #
    # Permission is hereby granted, free of charge, to any person
    # obtaining a copy of this software and associated documentation files
    # (the "Software"), to deal in the Software without restriction,
    # including without limitation the rights to use, copy, modify, merge,
    # publish, distribute, sublicense, and/or sell copies of the Software,
    # and to permit persons to whom the Software is furnished to do so,
    # subject to the following conditions:
    #
    # The above copyright notice and this permission notice shall be
    # included in all copies or substantial portions of the Software.
    #
    # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
    # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
    # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    # SOFTWARE.

    # You can skip over reading this class, if you like.
    # It's an implementation of mine of the bash parser in pure python
    # This has advantages over shlex, glob, and shlex->glob in that it expects
  2. Michael Lynn revised this gist Nov 26, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion shellista.py
    Original file line number Diff line number Diff line change
    @@ -664,7 +664,7 @@ def do_ungzip(self, line, gunzip=False):
    elif os.path.basename(filename).lower().endswith('.tgz'):
    altpath = os.path.splitext(os.path.basename(filename))[0] + '.tar'
    else:
    altpath = os.path.basename(filename) + '_ungziped'
    altpath = os.path.basename(filename) + '_ungzipped'
    altpath = os.path.join(os.path.dirname(filename), altpath)
    location = (args[1:2] or [altpath])[0]
    if os.path.exists(location):
  3. Michael Lynn revised this gist Nov 26, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion shellista.py
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    import os, cmd, sys, re, glob, os.path, shutil, zipfile, shutil, tarfile, gzip
    import os, cmd, sys, re, glob, os.path, shutil, zipfile, tarfile, gzip

    # You can skip over reading this class, if you like.
    # It's an implementation of mine of the bash parser in pure python
  4. Michael Lynn revised this gist Nov 26, 2012. 1 changed file with 54 additions and 4 deletions.
    58 changes: 54 additions & 4 deletions shellista.py
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    import os, cmd, sys, re, glob, os.path, shutil, zipfile, shutil, tarfile
    import os, cmd, sys, re, glob, os.path, shutil, zipfile, shutil, tarfile, gzip

    # You can skip over reading this class, if you like.
    # It's an implementation of mine of the bash parser in pure python
    @@ -589,7 +589,7 @@ def do_untar(self, line):
    return
    elif not os.path.exists(location):
    os.makedirs(location)
    if True:
    try:
    tar = tarfile.open(filename, 'r')
    # check for a leading directory common to all files and remove it
    dirnames = [os.path.join(os.path.dirname(x.name), '') for x in tar.getmembers() if x.name != 'pax_global_header']
    @@ -626,12 +626,62 @@ def do_untar(self, line):
    with open(fn, 'wb') as destfp:
    shutil.copyfileobj(fp, destfp)
    fp.close()
    if False:
    except Exception:
    tar.close()
    print "untar: %s: tar file is corrupt" % args[0]
    return
    if True:
    finally:
    tar.close()
    def do_ungzip(self, line, gunzip=False):
    """ungzip a gzip archive"""
    # filename with optional output filename
    fname = 'ungzip'
    if gunzip:
    fname = 'gunzip'
    args = self.bash(line)
    if args is None:
    return
    elif not (1 <= len(args) <= 2):
    print "%s: Usage: %s file [outfile]" % (fname, fname)
    else:
    filename = os.path.abspath(args[0])
    if not os.path.isfile(filename):
    print "%s: %s: No such file" % (fname,args[0])
    else:
    # '\x1f\x8b\x08' magic marker check
    f = open(filename, 'rb')
    try:
    gz_check = f.read(3)
    except Exception:
    gz_check = ''
    finally:
    f.close()
    if gz_check != '\x1f\x8b\x08':
    print "%s: %s: does not appear to be a gzip file" % (fname,args[0])
    else:
    if (os.path.basename(filename).lower().endswith('.gz') or os.path.basename(filename).lower().endswith('.gzip')):
    altpath = os.path.splitext(os.path.basename(filename))[0]
    elif os.path.basename(filename).lower().endswith('.tgz'):
    altpath = os.path.splitext(os.path.basename(filename))[0] + '.tar'
    else:
    altpath = os.path.basename(filename) + '_ungziped'
    altpath = os.path.join(os.path.dirname(filename), altpath)
    location = (args[1:2] or [altpath])[0]
    if os.path.exists(location):
    print "%s: %s: destination already exists" % (fname,os.path.basename(location))
    return
    dirf = os.path.dirname(os.path.dirname(os.path.abspath(location)))
    try:
    if not os.path.exists(dirf):
    os.makedirs(dirf)
    with open(location, 'wb') as outfile:
    with gzip.open(filename, 'rb') as gzfile:
    outfile.write(gzfile.read())
    except Exception:
    print "%s: %s: gzip file is corrupt" % (fname, args[0])
    def do_gunzip(self, line):
    """ungzip a gzip archive"""
    self.do_ungzip(line, gunzip=True)
    def do_quit(self,line):
    self.did_quit = True
    def do_q(self,line):
  5. Michael Lynn revised this gist Nov 26, 2012. 1 changed file with 156 additions and 76 deletions.
    232 changes: 156 additions & 76 deletions shellista.py
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    import os, cmd, sys, re, glob, os.path, shutil, zipfile
    import os, cmd, sys, re, glob, os.path, shutil, zipfile, shutil, tarfile

    # You can skip over reading this class, if you like.
    # It's an implementation of mine of the bash parser in pure python
    @@ -278,39 +278,39 @@ def do_mv(self, line):
    print "cp: %s: No such directory" % self.pprint(dest)
    else:
    full_dest = os.path.abspath(dest).rstrip('/') + '/'
    for file in files:
    full_file = os.path.abspath(file).rstrip('/')
    for filef in files:
    full_file = os.path.abspath(filef).rstrip('/')
    file_name = os.path.basename(full_file)
    new_name = os.path.join(full_dest,file_name)
    if (not os.path.exists(full_file)):
    print "! Error: Skipped, missing -", self.pprint(file)
    print "! Error: Skipped, missing -", self.pprint(filef)
    continue
    try:
    os.rename(full_file,new_name)
    except Exception:
    print "mv: %s: Unable to move" % self.pprint(file)
    print "mv: %s: Unable to move" % self.pprint(filef)
    else:
    # Moving a single file to a (pre-existing) directory or a file
    file = files[0]
    full_file = os.path.abspath(file).rstrip('/')
    filef = files[0]
    full_file = os.path.abspath(filef).rstrip('/')
    file_name = os.path.basename(full_file)
    full_dest = os.path.abspath(dest).rstrip('/')
    if (os.path.isdir(full_dest)):
    if (os.path.exists(full_file)):
    try:
    os.rename(full_file, full_dest + '/' + file_name)
    except:
    print "mv: %s: Unable to move" % self.pprint(file)
    print "mv: %s: Unable to move" % self.pprint(filef)
    else:
    print "mv: %s: No such file" % self.pprint(file)
    print "mv: %s: No such file" % self.pprint(filef)
    else:
    if (os.path.exists(full_file)):
    try:
    os.rename(full_file, full_dest)
    except:
    print "mv: %s: Unable to move" % self.pprint(file)
    print "mv: %s: Unable to move" % self.pprint(filef)
    else:
    print "mv: %s: No such file" % self.pprint(file)
    print "mv: %s: No such file" % self.pprint(filef)
    def do_cp(self, line):
    """copy files and directories"""
    args = self.bash(line)
    @@ -331,24 +331,24 @@ def do_cp(self, line):
    print "cp: %s: No such directory" % self.pprint(dest)
    else:
    full_dest = os.path.abspath(dest).rstrip('/') + '/'
    for file in files:
    full_file = os.path.abspath(file).rstrip('/')
    for filef in files:
    full_file = os.path.abspath(filef).rstrip('/')
    file_name = os.path.basename(full_file)
    new_name = os.path.join(full_dest,file_name)
    if (not os.path.exists(full_file)):
    print "! Error: Skipped, missing -", self.pprint(file)
    print "! Error: Skipped, missing -", self.pprint(filef)
    continue
    try:
    if (os.path.isdir(full_file)):
    shutil.copytree(full_file,new_name)
    else:
    shutil.copy(full_file,new_name)
    except Exception:
    print "cp: %s: Unable to copy" % self.pprint(file)
    print "cp: %s: Unable to copy" % self.pprint(filef)
    else:
    # Copying a single file to a (pre-existing) directory or a file
    file = files[0]
    full_file = os.path.abspath(file).rstrip('/')
    filef = files[0]
    full_file = os.path.abspath(filef).rstrip('/')
    file_name = os.path.basename(full_file)
    full_dest = os.path.abspath(dest).rstrip('/')
    new_name = os.path.join(full_dest,file_name)
    @@ -361,31 +361,31 @@ def do_cp(self, line):
    else:
    shutil.copy(full_file,new_name)
    except:
    print "cp: %s: Unable to copy" % self.pprint(file)
    print "cp: %s: Unable to copy" % self.pprint(filef)
    else:
    print "cp: %s: No such file" % self.pprint(file)
    print "cp: %s: No such file" % self.pprint(filef)
    elif (os.path.exists(full_dest)):
    # Destination is a file
    if (os.path.exists(full_file)):
    try:
    shutil.copy(full_file,full_dest)
    except:
    print "cp: %s: Unable to copy" % self.pprint(file)
    print "cp: %s: Unable to copy" % self.pprint(filef)
    else:
    print "cp: %s: No such file" % self.pprint(file)
    print "cp: %s: No such file" % self.pprint(filef)
    else:
    if (os.path.isdir(full_file)):
    # Source is a directory, destination should become a directory
    try:
    shutil.copytree(full_file,full_dest)
    except:
    print "cp: %s: Unable to copy" % self.pprint(file)
    print "cp: %s: Unable to copy" % self.pprint(filef)
    else:
    # Source is a file, destination should become a file
    try:
    shutil.copy(full_file,full_dest)
    except:
    print "cp: %s: Unable to copy" % self.pprint(file)
    print "cp: %s: Unable to copy" % self.pprint(filef)

    def do_rm(self, line):
    """remove one or more files/directories"""
    @@ -395,23 +395,23 @@ def do_rm(self, line):
    elif (len(args) < 1):
    print "rm: Usage: rm file_or_dir [...]"
    else:
    for file in args:
    full_file = os.path.abspath(file).rstrip('/')
    if not os.path.exists(file):
    print "! Skipping: Not found -", self.pprint(file)
    for filef in args:
    full_file = os.path.abspath(filef).rstrip('/')
    if not os.path.exists(filef):
    print "! Skipping: Not found -", self.pprint(filef)
    continue
    if (os.path.isdir(full_file)):
    try:
    shutil.rmtree(full_file, True)
    if (os.path.exists(full_file)):
    print "rm: %s: Unable to remove" % self.pprint(file)
    print "rm: %s: Unable to remove" % self.pprint(filef)
    except Exception:
    print "rm: %s: Unable to remove" % self.pprint(file)
    print "rm: %s: Unable to remove" % self.pprint(filef)
    else:
    try:
    os.remove(full_file)
    except Exception:
    print "rm: %s: Unable to remove" % self.pprint(file)
    print "rm: %s: Unable to remove" % self.pprint(filef)
    def do_cat(self, line):
    """print file"""
    args = self.bash(line)
    @@ -442,12 +442,12 @@ def do_ls(self, line):
    elif (not files):
    files = ['.']
    files_for_path = dict()
    for file in files:
    full_file = os.path.abspath(file).rstrip('/')
    for filef in files:
    full_file = os.path.abspath(filef).rstrip('/')
    file_name = os.path.basename(full_file)
    dir_name = os.path.dirname(full_file).rstrip('/')
    if (not os.path.exists(full_file)):
    print "! Error: Skipped, missing -", self.pprint(file)
    print "! Error: Skipped, missing -", self.pprint(filef)
    continue
    if (os.path.isdir(full_file)):
    # Need to add this as a key and all the files contained inside it
    @@ -473,14 +473,15 @@ def do_ls(self, line):
    print "\n" + self.pprint(path) + "/:"
    elif (not in_cwd):
    print self.pprint(path) + "/:"
    for file in sorted(list(files_for_path[path])):
    full_file = os.path.abspath(file).rstrip('/')
    for filef in sorted(list(files_for_path[path])):
    full_file = os.path.abspath(filef).rstrip('/')
    file_name = os.path.basename(full_file)
    if (os.path.isdir(full_file)):
    print file_name + "/"
    else:
    print file_name + (" (%s)" % (self.sizeof_fmt(os.stat(full_file).st_size)))
    def do_unzip(self, line):
    """unzip a zip archive"""
    # filename with optional destination
    args = self.bash(line)
    if args is None:
    @@ -501,57 +502,136 @@ def do_unzip(self, line):
    finally:
    f.close()
    if pk_check != 'PK':
    print "unzip: %s: zip file is corrupt" % args[0]
    print "unzip: %s: does not appear to be a zip file" % args[0]
    else:
    if (os.path.basename(filename).lower().endswith('.zip')):
    altpath = os.path.splitext(os.path.basename(filename))[0]
    else:
    altpath = os.path.basename(filename) + '_unzipped'
    altpath = os.path.join(os.path.dirname(filename), altpath)
    location = (args[1:2] or [altpath])[0]
    if (os.path.exists(location)) and not (os.path.isdir(location)):
    print "unzip: %s: destination is not a directory" % location
    return
    elif not os.path.exists(location):
    os.makedirs(location)
    zipfp = open(filename, 'rb')
    if (os.path.exists(location)) and not (os.path.isdir(location)):
    print "unzip: %s: destination is not a directory" % location
    return
    elif not os.path.exists(location):
    os.makedirs(location)
    zipfp = open(filename, 'rb')
    try:
    zipf = zipfile.ZipFile(zipfp)
    # check for a leading directory common to all files and remove it
    dirnames = [os.path.join(os.path.dirname(x), '') for x in zipf.namelist()]
    common_dir = os.path.commonprefix(dirnames or ['/'])
    # Check to make sure there aren't 2 or more sub directories with the same prefix
    if not common_dir.endswith('/'):
    common_dir = os.path.join(os.path.dirname(common_dir), '')
    for name in zipf.namelist():
    data = zipf.read(name)
    fn = name
    if common_dir:
    if fn.startswith(common_dir):
    fn = fn.split(common_dir, 1)[-1]
    elif fn.startswith('/' + common_dir):
    fn = fn.split('/' + common_dir, 1)[-1]
    fn = fn.lstrip('/')
    fn = os.path.join(location, fn)
    dirf = os.path.dirname(fn)
    if not os.path.exists(dirf):
    os.makedirs(dirf)
    if fn.endswith('/'):
    # A directory
    if not os.path.exists(fn):
    os.makedirs(fn)
    else:
    fp = open(fn, 'wb')
    try:
    fp.write(data)
    finally:
    fp.close()
    except Exception:
    zipfp.close()
    print "unzip: %s: zip file is corrupt" % args[0]
    return
    finally:
    zipfp.close()
    def do_untar(self, line):
    """untar a tar archive"""
    # filename with optional destination
    args = self.bash(line)
    if args is None:
    return
    elif not (1 <= len(args) <= 2):
    print "untar: Usage: untar file [destination]"
    else:
    filename = os.path.abspath(args[0])
    if not os.path.isfile(filename):
    print "untar: %s: No such file" % args[0]
    else:
    # 'ustar' magic marker check
    f = open(filename)
    try:
    zip = zipfile.ZipFile(zipfp)
    # check for a leading directory common to all files and remove it
    dirnames = [os.path.join(os.path.dirname(x), '') for x in zip.namelist()]
    common_dir = os.path.commonprefix(dirnames or ['/'])
    # Check to make sure there aren't 2 or more sub directories with the same prefix
    if not common_dir.endswith('/'):
    common_dir = os.path.join(os.path.dirname(common_dir), '')
    for name in zip.namelist():
    data = zip.read(name)
    fn = name
    if common_dir:
    if fn.startswith(common_dir):
    fn = fn.split(common_dir, 1)[-1]
    elif fn.startswith('/' + common_dir):
    fn = fn.split('/' + common_dir, 1)[-1]
    fn = fn.lstrip('/')
    fn = os.path.join(location, fn)
    dir = os.path.dirname(fn)
    if not os.path.exists(dir):
    os.makedirs(dir)
    if fn.endswith('/'):
    # A directory
    if not os.path.exists(fn):
    os.makedirs(fn)
    else:
    fp = open(fn, 'wb')
    try:
    fp.write(data)
    finally:
    fp.close()
    f.seek(257)
    ustar_check = f.read(5)
    except Exception:
    zipfp.close()
    print "unzip: %s: zip file is corrupt" % args[0]
    return
    ustar_check = ''
    finally:
    zipfp.close()
    f.close()
    if ustar_check != 'ustar':
    print "untar: %s: does not appear to be a tar file" % args[0]
    else:
    if (os.path.basename(filename).lower().endswith('.tar')):
    altpath = os.path.splitext(os.path.basename(filename))[0]
    else:
    altpath = os.path.basename(filename) + '_untarred'
    altpath = os.path.join(os.path.dirname(filename), altpath)
    location = (args[1:2] or [altpath])[0]
    if (os.path.exists(location)) and not (os.path.isdir(location)):
    print "untar: %s: destination is not a directory" % location
    return
    elif not os.path.exists(location):
    os.makedirs(location)
    if True:
    tar = tarfile.open(filename, 'r')
    # check for a leading directory common to all files and remove it
    dirnames = [os.path.join(os.path.dirname(x.name), '') for x in tar.getmembers() if x.name != 'pax_global_header']
    common_dir = os.path.commonprefix(dirnames or ['/'])
    if not common_dir.endswith('/'):
    common_dir = os.path.join(os.path.dirname(common_dir), '')
    for member in tar.getmembers():
    fn = member.name
    if fn == 'pax_global_header':
    continue
    if common_dir:
    if fn.startswith(common_dir):
    fn = fn.split(common_dir, 1)[-1]
    elif fn.startswith('/' + common_dir):
    fn = fn.split('/' + common_dir, 1)[-1]
    fn = fn.lstrip('/')
    fn = os.path.join(location, fn)
    dirf = os.path.dirname(fn)
    if member.isdir():
    # A directory
    if not os.path.exists(fn):
    os.makedirs(fn)
    elif member.issym():
    # skip symlinks
    continue
    else:
    try:
    fp = tar.extractfile(member)
    except (KeyError, AttributeError):
    # invalid member, not necessarily a bad tar file
    continue
    if not os.path.exists(dirf):
    os.makedirs(dirf)
    with open(fn, 'wb') as destfp:
    shutil.copyfileobj(fp, destfp)
    fp.close()
    if False:
    tar.close()
    print "untar: %s: tar file is corrupt" % args[0]
    return
    if True:
    tar.close()
    def do_quit(self,line):
    self.did_quit = True
    def do_q(self,line):
  6. Michael Lynn revised this gist Nov 26, 2012. 1 changed file with 93 additions and 10 deletions.
    103 changes: 93 additions & 10 deletions shellista.py
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    import os, cmd, sys, re, glob, os.path, shutil
    import os, cmd, sys, re, glob, os.path, shutil, zipfile

    # You can skip over reading this class, if you like.
    # It's an implementation of mine of the bash parser in pure python
    @@ -319,8 +319,12 @@ def do_cp(self, line):
    elif (not (len(args) >= 2)):
    print "cp: Usage: cp src [..] dest"
    else:
    dest = args[-1]
    files = args[0:-1]
    if len(args) > 2:
    files = args[:-1]
    dest = args[-1]
    else:
    files = args[:1]
    dest = args[-1]
    if (len(files) > 1):
    # Copying multiple files, destination must be an existing directory.
    if (not os.path.isdir(dest)):
    @@ -370,11 +374,19 @@ def do_cp(self, line):
    else:
    print "cp: %s: No such file" % self.pprint(file)
    else:
    # Destination should become a directory
    try:
    shutil.copytree(full_file,full_dest)
    except:
    print "cp: %s: Unable to copy" % self.pprint(file)
    if (os.path.isdir(full_file)):
    # Source is a directory, destination should become a directory
    try:
    shutil.copytree(full_file,full_dest)
    except:
    print "cp: %s: Unable to copy" % self.pprint(file)
    else:
    # Source is a file, destination should become a file
    try:
    shutil.copy(full_file,full_dest)
    except:
    print "cp: %s: Unable to copy" % self.pprint(file)

    def do_rm(self, line):
    """remove one or more files/directories"""
    args = self.bash(line)
    @@ -468,7 +480,78 @@ def do_ls(self, line):
    print file_name + "/"
    else:
    print file_name + (" (%s)" % (self.sizeof_fmt(os.stat(full_file).st_size)))

    def do_unzip(self, line):
    # filename with optional destination
    args = self.bash(line)
    if args is None:
    return
    elif not (1 <= len(args) <= 2):
    print "unzip: Usage: unzip file [destination]"
    else:
    filename = os.path.abspath(args[0])
    if not os.path.isfile(filename):
    print "unzip: %s: No such file" % args[0]
    else:
    # PK magic marker check
    f = open(filename)
    try:
    pk_check = f.read(2)
    except Exception:
    pk_check = ''
    finally:
    f.close()
    if pk_check != 'PK':
    print "unzip: %s: zip file is corrupt" % args[0]
    else:
    if (os.path.basename(filename).lower().endswith('.zip')):
    altpath = os.path.splitext(os.path.basename(filename))[0]
    else:
    altpath = os.path.basename(filename) + '_unzipped'
    altpath = os.path.join(os.path.dirname(filename), altpath)
    location = (args[1:2] or [altpath])[0]
    if (os.path.exists(location)) and not (os.path.isdir(location)):
    print "unzip: %s: destination is not a directory" % location
    return
    elif not os.path.exists(location):
    os.makedirs(location)
    zipfp = open(filename, 'rb')
    try:
    zip = zipfile.ZipFile(zipfp)
    # check for a leading directory common to all files and remove it
    dirnames = [os.path.join(os.path.dirname(x), '') for x in zip.namelist()]
    common_dir = os.path.commonprefix(dirnames or ['/'])
    # Check to make sure there aren't 2 or more sub directories with the same prefix
    if not common_dir.endswith('/'):
    common_dir = os.path.join(os.path.dirname(common_dir), '')
    for name in zip.namelist():
    data = zip.read(name)
    fn = name
    if common_dir:
    if fn.startswith(common_dir):
    fn = fn.split(common_dir, 1)[-1]
    elif fn.startswith('/' + common_dir):
    fn = fn.split('/' + common_dir, 1)[-1]
    fn = fn.lstrip('/')
    fn = os.path.join(location, fn)
    dir = os.path.dirname(fn)
    if not os.path.exists(dir):
    os.makedirs(dir)
    if fn.endswith('/'):
    # A directory
    if not os.path.exists(fn):
    os.makedirs(fn)
    else:
    fp = open(fn, 'wb')
    try:
    fp.write(data)
    finally:
    fp.close()
    except Exception:
    zipfp.close()
    print "unzip: %s: zip file is corrupt" % args[0]
    return
    finally:
    zipfp.close()
    def do_quit(self,line):
    self.did_quit = True
    def do_q(self,line):
    @@ -488,4 +571,4 @@ def main():
    shell.cmdloop()

    if __name__ == '__main__':
    main()
    main()
  7. Michael Lynn created this gist Nov 24, 2012.
    491 changes: 491 additions & 0 deletions shellista.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,491 @@
    import os, cmd, sys, re, glob, os.path, shutil

    # You can skip over reading this class, if you like.
    # It's an implementation of mine of the bash parser in pure python
    # This has advantages over shlex, glob, and shlex->glob in that it expects
    # the strings to represent files from the start.

    class BetterParser:
    def __init__(self):
    self.env_vars = {"$HOME": os.path.expanduser('~')}
    def parse(self, instr):
    instr = instr.rstrip('\r\n\t ')
    # Handle all three steps of parsing:
    # 1: Quoting
    # 2: Expansion (vars, ~, and glob.glob)
    # 3: Splitting
    if (not instr):
    return []
    parse_array = [[],[]]
    parse_state = 0
    # Stage 1: Process quotes
    last_block = []
    for i,c in enumerate(instr):
    if (parse_state == 0):
    # Base state, look for quotes that haven't been escaped
    if (c == '\\'):
    # Switch to special mode to escape the next character
    last_block.append(i)
    parse_state += 3
    elif (c == '"'):
    # Start double quoting
    last_block.append(i)
    parse_state = 1
    elif (c == "'"):
    # Start single quoting
    last_block.append(i)
    parse_state = 2
    else:
    parse_array[0].append(c)
    parse_array[1].append(0)
    elif (parse_state == 1):
    if (c not in '$\\"'):
    parse_array[0].append(c)
    parse_array[1].append(1)
    elif (c == '$'):
    parse_array[0].append(c)
    parse_array[1].append(0)
    elif (c == '\\'):
    last_block.append(i)
    parse_state += 3
    else:
    last_block.pop()
    parse_state = 0
    elif (parse_state == 2):
    if (c != "'"):
    parse_array[0].append(c)
    parse_array[1].append(1)
    else:
    last_block.pop()
    parse_state = 0
    elif (3 <= parse_state <= 4):
    last_block.pop()
    parse_array[0].append(c)
    parse_array[1].append(parse_state + 0)
    parse_state -= 3
    if (1 <= parse_state <= 2):
    raise SyntaxError("Unbalanced quotes at char %s: %s <--" % (last_block[-1],instr[:(last_block[-1]+1)]))
    elif (parse_state == 3):
    raise SyntaxError("Unfinished backslash escape at char %s: %s <--" % (last_block[-1],instr[:(last_block[-1]+1)]))
    elif (parse_state == 4):
    raise SyntaxError("Unfinished backslash escape within double quotes at char %s: %s <--" % (last_block[-1],instr[:(last_block[-1]+1)]))
    # State 1.5: Rebuild the parse array, evaluating escaped characters
    temp_array = [[],[]]
    escapes = {'t': '\t', 'r': '\r', 'n': '\n'}
    for i,c in enumerate(parse_array[0]):
    if (3 <= parse_array[1][i] <= 4):
    temp_array[0].append(escapes.get(c,c))
    temp_array[1].append(1)
    else:
    temp_array[0].append(c)
    temp_array[1].append(parse_array[1][i] + 0)
    parse_array = temp_array
    # Stage 2: Perform expansions
    for i,c in enumerate(parse_array[0]):
    if ((c == '$') and (parse_array[1][i] == 0)):
    # Unquoted $ detected
    remainder = ''.join(parse_array[0][i:])
    for var_name in self.env_vars.keys():
    if remainder.startswith(var_name):
    # Found a variable that needs to be replaced
    # Blow out the variable name
    for j in range(i,len(var_name)+i):
    parse_array[0][j] = ''
    parse_array[1][j] = -1
    # Insert the new value
    parse_array[0][i] = self.env_vars[var_name]
    parse_array[1][i] = 2
    elif ((c == '~') and (parse_array[1][i] == 0)):
    # Unquoted ~ detected, make sure it's dir-ish
    if ((i == 0) and (len(parse_array[0]) == 1)):
    # Tilde by itself
    parse_array[0][i] = self.env_vars.get('$HOME', '/')
    parse_array[1][i] = 2
    elif ((i == 0) and ((parse_array[0][i+1] == '/') or ((parse_array[0][i+1] == ' ') and (parse_array[1][i+1] == 0)))):
    # Tilde at start, followed by slash or non-escaped space
    parse_array[0][i] = self.env_vars.get('$HOME', '/')
    parse_array[1][i] = 2
    elif ((len(parse_array[0]) == (i+1)) and ((parse_array[0][i-1] == ' ') and (parse_array[1][i-1] == 0))):
    # Tilde at end, preceded by a non-escaped space
    parse_array[0][i] = self.env_vars.get('$HOME', '/')
    parse_array[1][i] = 2
    elif (((parse_array[0][i-1] == ' ') and (parse_array[1][i-1] == 0)) and ((parse_array[0][i+1] == '/') or ((parse_array[0][i+1] == ' ') and (parse_array[1][i+1] == 0)))):
    # Tilde not at start or end, preceded by a non-escaped space and followed by slash or non-escaped space
    parse_array[0][i] = self.env_vars.get('$HOME', '/')
    parse_array[1][i] = 2
    # Stage 2.5: Rebuild the parse array, finalizing expansions
    temp_array = [[],[]]
    for i,c in enumerate(parse_array[0]):
    if (parse_array[1][i] == 2):
    for d in c:
    temp_array[0].append(d)
    temp_array[1].append(1)
    elif (parse_array[1][i] >= 0):
    temp_array[0].append(c)
    temp_array[1].append(parse_array[1][i] + 0)
    parse_array = temp_array
    # Stage 2.7: Wildcard globbing
    temp_groups = [[],[]]
    split_mode = 0
    for i,c in enumerate(parse_array[0]):
    # Pre-split into words based on non-escaped whitespace
    if (not ((c in ' \t\n\r') and (parse_array[1][i] == 0))):
    if (split_mode == 0):
    split_mode = 1
    temp_groups[0].append([c])
    temp_groups[1].append([0 + parse_array[1][i]])
    else:
    temp_groups[0][-1].append(c)
    temp_groups[1][-1].append(parse_array[1][i])
    else:
    split_mode = 0
    temp_groups[0].append([c])
    temp_groups[1].append([0 + parse_array[1][i]])
    temp_array = [[],[]]
    seen_first = False
    not_whitespace = False
    for i,chunk in enumerate(temp_groups[0]):
    # Iterate through words looking for unescaped glob characters
    glob_chunk = False
    for j,c in enumerate(chunk):
    if ((c in '*[]?') and (temp_groups[1][i][j] == 0)):
    # Found a chunk with unescaped glob character, but it's not the first chunk
    if (seen_first):
    glob_chunk = True
    if (not seen_first):
    if (not ((c in ' \t\n\r') and (temp_groups[1][i][j] == 0))):
    not_whitespace = True
    if (not_whitespace):
    seen_first = True
    not_whitespace = False
    if (glob_chunk):
    # Found a glob containing chunk
    glob_str = ''.join(chunk)
    matches = glob.glob(glob_str)
    if (matches):
    for match in matches:
    for c in match:
    temp_array[0].append(c)
    temp_array[1].append(1)
    temp_array[0].append(' ')
    temp_array[1].append(0)
    _ = temp_array[0].pop()
    _ = temp_array[1].pop()
    else:
    for j,c in enumerate(chunk):
    temp_array[0].append(c)
    temp_array[1].append(temp_groups[1][i][j])
    else:
    for j,c in enumerate(chunk):
    temp_array[0].append(c)
    temp_array[1].append(temp_groups[1][i][j])
    parse_array = temp_array
    # Stage 2.9: Rebuild the parse array, escaping remaining non-whitespace
    temp_array = [[],[]]
    for i,c in enumerate(parse_array[0]):
    if ((c not in ' \t\n\r') and (parse_array[1][i] == 0)):
    temp_array[0].append(c)
    temp_array[1].append(1)
    else:
    temp_array[0].append(c)
    temp_array[1].append(parse_array[1][i] + 0)
    parse_array = temp_array
    # Stage 3: Splitting
    split_mode = 0
    final_value = []
    for i,c in enumerate(parse_array[0]):
    if (parse_array[1][i] == 1):
    if (split_mode == 0):
    split_mode = 1
    final_value.append('' + c)
    else:
    final_value[-1] += c
    else:
    split_mode = 0
    return final_value

    class Shell(cmd.Cmd):
    def __init__(self):
    cmd.Cmd.__init__(self)
    self._bash = BetterParser()
    self._bash.env_vars['$HOME'] = os.path.expanduser('~/Documents')
    self.did_quit = False
    def bash(self, argstr):
    try:
    return self._bash.parse('. ' + argstr)[1:]
    except SyntaxError, e:
    print "Syntax Error: %s" % e
    return None
    def pprint(self, path):
    if (path.startswith(self._bash.env_vars['$HOME'])):
    return '~' + path.split(self._bash.env_vars['$HOME'],1)[-1]
    return path
    def do_pwd(self, line):
    """return working directory name"""
    print self.pprint(os.getcwd())
    def do_cd(self, line):
    """change the current directory to DIR"""
    args = self.bash(line)
    if args is None:
    return
    elif args and len(args) == 1:
    try:
    os.chdir(args[0])
    except Exception:
    print "cd: %s: No such directory" % line
    elif len(args) > 1:
    print "cd: Too many arguments"
    else:
    os.chdir(self._bash.env_vars['$HOME'])
    def sizeof_fmt(self, num):
    for x in ['bytes','KB','MB','GB']:
    if num < 1024.0:
    if (x == 'bytes'):
    return "%s %s" % (num, x)
    else:
    return "%3.1f %s" % (num, x)
    num /= 1024.0
    return "%3.1f%s" % (num, 'TB')
    def do_mkdir(self, line):
    """make a directory"""
    args = self.bash(line)
    if args is None:
    return
    elif (len(args) == 1):
    target = args[0]
    if os.path.exists(target):
    print "mkdir: %s: File exists" % line
    else:
    try:
    os.mkdir(target)
    except Exception:
    print "mkdir: %s: Unable to create" % line
    else:
    print "mkdir: Usage: mkdir directory_name"
    def do_mv(self, line):
    """move files and directories"""
    args = self.bash(line)
    if args is None:
    return
    elif (not (len(args) >= 2)):
    print "mv: Usage: mv src [..] dest"
    else:
    dest = args[-1]
    files = args[0:-1]
    if (len(files) > 1):
    # Moving multiple files, destination must be an existing directory.
    if (not os.path.isdir(dest)):
    print "cp: %s: No such directory" % self.pprint(dest)
    else:
    full_dest = os.path.abspath(dest).rstrip('/') + '/'
    for file in files:
    full_file = os.path.abspath(file).rstrip('/')
    file_name = os.path.basename(full_file)
    new_name = os.path.join(full_dest,file_name)
    if (not os.path.exists(full_file)):
    print "! Error: Skipped, missing -", self.pprint(file)
    continue
    try:
    os.rename(full_file,new_name)
    except Exception:
    print "mv: %s: Unable to move" % self.pprint(file)
    else:
    # Moving a single file to a (pre-existing) directory or a file
    file = files[0]
    full_file = os.path.abspath(file).rstrip('/')
    file_name = os.path.basename(full_file)
    full_dest = os.path.abspath(dest).rstrip('/')
    if (os.path.isdir(full_dest)):
    if (os.path.exists(full_file)):
    try:
    os.rename(full_file, full_dest + '/' + file_name)
    except:
    print "mv: %s: Unable to move" % self.pprint(file)
    else:
    print "mv: %s: No such file" % self.pprint(file)
    else:
    if (os.path.exists(full_file)):
    try:
    os.rename(full_file, full_dest)
    except:
    print "mv: %s: Unable to move" % self.pprint(file)
    else:
    print "mv: %s: No such file" % self.pprint(file)
    def do_cp(self, line):
    """copy files and directories"""
    args = self.bash(line)
    if args is None:
    return
    elif (not (len(args) >= 2)):
    print "cp: Usage: cp src [..] dest"
    else:
    dest = args[-1]
    files = args[0:-1]
    if (len(files) > 1):
    # Copying multiple files, destination must be an existing directory.
    if (not os.path.isdir(dest)):
    print "cp: %s: No such directory" % self.pprint(dest)
    else:
    full_dest = os.path.abspath(dest).rstrip('/') + '/'
    for file in files:
    full_file = os.path.abspath(file).rstrip('/')
    file_name = os.path.basename(full_file)
    new_name = os.path.join(full_dest,file_name)
    if (not os.path.exists(full_file)):
    print "! Error: Skipped, missing -", self.pprint(file)
    continue
    try:
    if (os.path.isdir(full_file)):
    shutil.copytree(full_file,new_name)
    else:
    shutil.copy(full_file,new_name)
    except Exception:
    print "cp: %s: Unable to copy" % self.pprint(file)
    else:
    # Copying a single file to a (pre-existing) directory or a file
    file = files[0]
    full_file = os.path.abspath(file).rstrip('/')
    file_name = os.path.basename(full_file)
    full_dest = os.path.abspath(dest).rstrip('/')
    new_name = os.path.join(full_dest,file_name)
    if (os.path.isdir(full_dest)):
    # Destination is a directory already
    if (os.path.exists(full_file)):
    try:
    if (os.path.isdir(full_file)):
    shutil.copytree(full_file,new_name)
    else:
    shutil.copy(full_file,new_name)
    except:
    print "cp: %s: Unable to copy" % self.pprint(file)
    else:
    print "cp: %s: No such file" % self.pprint(file)
    elif (os.path.exists(full_dest)):
    # Destination is a file
    if (os.path.exists(full_file)):
    try:
    shutil.copy(full_file,full_dest)
    except:
    print "cp: %s: Unable to copy" % self.pprint(file)
    else:
    print "cp: %s: No such file" % self.pprint(file)
    else:
    # Destination should become a directory
    try:
    shutil.copytree(full_file,full_dest)
    except:
    print "cp: %s: Unable to copy" % self.pprint(file)
    def do_rm(self, line):
    """remove one or more files/directories"""
    args = self.bash(line)
    if args is None:
    return
    elif (len(args) < 1):
    print "rm: Usage: rm file_or_dir [...]"
    else:
    for file in args:
    full_file = os.path.abspath(file).rstrip('/')
    if not os.path.exists(file):
    print "! Skipping: Not found -", self.pprint(file)
    continue
    if (os.path.isdir(full_file)):
    try:
    shutil.rmtree(full_file, True)
    if (os.path.exists(full_file)):
    print "rm: %s: Unable to remove" % self.pprint(file)
    except Exception:
    print "rm: %s: Unable to remove" % self.pprint(file)
    else:
    try:
    os.remove(full_file)
    except Exception:
    print "rm: %s: Unable to remove" % self.pprint(file)
    def do_cat(self, line):
    """print file"""
    args = self.bash(line)
    if args is None:
    return
    elif (len(args) != 1):
    print "cat: Usage: cat file"
    else:
    target = args[0]
    if (not os.path.exists(target)):
    print "cat: %s: No such file" % line
    elif (os.path.isdir(target)):
    print "cat: %s: Is a directory" % line
    else:
    try:
    contents = ""
    with open(target, 'r') as f:
    contents = f.read()
    print contents
    print ""
    except Exception:
    print "cat: %s: Unable to access" % line
    def do_ls(self, line):
    """list directory contents"""
    files = self.bash(line)
    if files is None:
    return
    elif (not files):
    files = ['.']
    files_for_path = dict()
    for file in files:
    full_file = os.path.abspath(file).rstrip('/')
    file_name = os.path.basename(full_file)
    dir_name = os.path.dirname(full_file).rstrip('/')
    if (not os.path.exists(full_file)):
    print "! Error: Skipped, missing -", self.pprint(file)
    continue
    if (os.path.isdir(full_file)):
    # Need to add this as a key and all the files contained inside it
    _dirs = files_for_path.get(full_file, set())
    for new_file in os.listdir(full_file):
    _dirs.add(full_file.rstrip('/') + '/' + new_file.rstrip('/'))
    files_for_path[full_file] = _dirs
    else:
    _dirs = files_for_path.get(dir_name, set())
    _dirs.add(full_file)
    files_for_path[dir_name] = _dirs
    # Iterate over the paths, in alphabetical order:
    paths = sorted(files_for_path.keys())
    cwd = os.getcwd().rstrip('/')
    in_cwd = False
    if (cwd in paths):
    # Move cwd to the front, mark that it's present
    paths.remove(cwd)
    paths = [cwd] + paths
    in_cwd = True
    for i,path in enumerate(paths):
    if (i > 0):
    print "\n" + self.pprint(path) + "/:"
    elif (not in_cwd):
    print self.pprint(path) + "/:"
    for file in sorted(list(files_for_path[path])):
    full_file = os.path.abspath(file).rstrip('/')
    file_name = os.path.basename(full_file)
    if (os.path.isdir(full_file)):
    print file_name + "/"
    else:
    print file_name + (" (%s)" % (self.sizeof_fmt(os.stat(full_file).st_size)))

    def do_quit(self,line):
    self.did_quit = True
    def do_q(self,line):
    self.did_quit = True
    def do_exit(self,line):
    self.did_quit = True
    def do_logout(self,line):
    self.did_quit = True
    def do_logoff(self,line):
    self.did_quit = True
    def postcmd(self,stop,line):
    return self.did_quit

    def main():
    shell = Shell()
    shell.prompt = '> '
    shell.cmdloop()

    if __name__ == '__main__':
    main()