#!/usr/bin/env python3 # # fl This script prints the first and last N lines from the output # of a command execution. # # usage: # fl [-... | -help | -Version] # # Revision History # 1.0 Initial revision. 140531 # 1.1 Added license info. 180616 # # Written by Wayne Morrison, 140524. # # Copyright 2014 Wayne Morrison # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # # Version information. # NAME = "fl" VERS = NAME + " version: 1.1" import argparse import subprocess #------------------------------------------------------------------------ DEF_COUNT = 10 # Default count of lines to print. linecnt = DEF_COUNT # Lines to print from command output. firstcnt = -1 # Lines to print from start of command output. lastcnt = -1 # Lines to print from end of command output. number = 0 # Line-numbering flag. #------------------------------------------------------------------------ # Routine: main() # # Purpose: Do shtuff. # def main(): # # Parse our command line for options and commands. # cmdline = getopts() # # Execute the user's command and save the output in an array. # lines = runcmd(cmdline) # # Figure out the lines to print and print 'em. # headtail(lines) #------------------------------------------------------------------------ # Routine: getopts() # # Purpose: Parse the command line for fl options and the command to # be executed. # def getopts(): global firstcnt # First-lines count. global lastcnt # Last-lines count. global linecnt # Lines count. global number # Line-numbering flag. # # Ensure we've got a command to look up. # # usagestr = '%(prog)s [options] ' usagestr = usage(0) # # Parse the options. # ap = argparse.ArgumentParser(usage=usagestr, add_help=False) # # Add the recognized options. # ap.add_argument('-count', type=int) ap.add_argument('-first', type=int) ap.add_argument('-last', type=int) ap.add_argument('-number', action='store_true') ap.add_argument('-Version', action='store_true') ap.add_argument('-help', action='store_true') ap.add_argument('cmd', nargs=argparse.REMAINDER) # # Now parse the options. # args = ap.parse_args() # # Check for some immediate options. # if(args.Version): version() if(args.help): usage(1) # # Ensure that -count is not used with -first or -last. # if((args.count != None) and ((args.first != None) or (args.last != None))): print("-count may not be used with -first or -last") exit(1) # # Save the option values. # number = args.number if(args.count != None): linecnt = args.count if(args.first != None): firstcnt = args.first if(args.last != None): lastcnt = args.last # # Set the first and last counts to the default if the -first # and -last options weren't given. # if(firstcnt == -1): firstcnt = linecnt if(lastcnt == -1): lastcnt = linecnt # # Ensure we've been given a command. # if(len(args.cmd) == 0): usage(1) # # Return the command and arguments from the argument list. # return(args.cmd) #------------------------------------------------------------------------ # Routine: runcmd() # # Purpose: Run the user's command and convert the output from # binary strings to a list of UTF-8 lines. # def runcmd(cmdline): # # Run the command. # try: bout = subprocess.check_output(cmdline) # # Handle OSErrors -- most likely an unrecognized command. # except OSError as exc: print(exc.strerror) exit(1); # # Handle CalledProcessErrors -- errors with the program we just ran. # except subprocess.CalledProcessError as exc: retcode = exc.returncode; exit(retcode); # # Convert the bytearray into a string, and then split the lines. # out = bout.decode("utf-8") lines = out.splitlines() # # Return the list of lines. # return(lines) #---------------------------------------------------------------------- # Routine: headtail() # # Purpose: Print the relevant line of the output. # If the length of the output is less than the length of the # first blob and the last blob, we'll print all the output. # Otherwise, we'll print the first blob and the last blob # separated by an empty line. # def headtail(outlines): global firstcnt # First-lines count. global lastcnt # Last-lines count. outset = [] # Set of lines to print. outlen = len(outlines) # Number of total output lines. totallines = firstcnt + lastcnt # Total count of lines to print. # # The output length is less than the length of the first # and last blobs, so we'll print all the output. # if(outlen <= totallines): for ind in range(outlen): outln(ind,outlines[ind]) else: # # Print the first blob of output. # for ind in range(firstcnt): outln(ind,outlines[ind]) # # Insert an empty line... # print('') # # Finish up with the last blob of output. # for ind in range(outlen - lastcnt, outlen): outln(ind,outlines[ind]) #---------------------------------------------------------------------- # Routine: outln() # # Purpose: Print a line of text, either with or without line numbers. # # Bump the line index since we're numbering the lines starting # at 1. Sure, that's a violation of natural law, but it matches # "cat -n" and it's probably a bit more intuitive to most people. # # This is simple for now, but it may get more complex # as additions to the output are needed. # def outln(ind,ln): global number # Line-numbering flag. if(number): # ind += 1 print("{}:\t{}".format(ind,ln)) else: print(ln) #---------------------------------------------------------------------- # Routine: version() # # Purpose: Print the version number(s) and exit. # def version(): print(VERS) exit(0) #---------------------------------------------------------------------- # Routine: usage() # # Purpose: Do something with the usage message. # # If the prtflag parameter is non-zero, we'll print and exit. # If it is zero, we'll just return the string. # def usage(prtflag): # # Set up our usage string. # outstr = """usage: fl [options] where [options] are: -count count - show count lines from start and end of output -first count - show count lines from start of output -last count - show count lines from end of output -number - number the output lines -Version - show version and exit -help - show usage message """ # # Just return the output if we aren't to print the usage string. # if(prtflag == 0): return(outstr) # # Print the usage string and exit. # print(outstr.rstrip()) exit(0) #------------------------------------------------------------------------ # # Do everything. # main() exit(0) #------------------------------------------------------------------------ ''' NAME fl - execute a command and display the first M and last N lines of output SYNOPSIS fl [options] DESCRIPTION fl executes a user-specified command and displays the first M and last N lines of the command's output. By default, ten lines from the beginning and end of the output will be printed, but these line counts may be changed by the -count, -first, and -last options. If the length of the output is less than the length of the first chunk and the last chunk, all the output lines will be printed. Otherwise, the first chunk and the last chunk will be printed, separated by an empty line. OPTIONS fl takes the following options: -count Show lines from start and end of output. By default, 10 lines will be printed from each. This option may not be used with the -first or -last option. -first Show lines from start of output. This option may not be used with the -count option. -last Show lines from end of output. This option may not be used with the -count option. -number Number the lines being displayed, starting from 1. (This mimics the "-n" option of the cat command.) -Version Display the version information for fl. -help Display a help message. LICENSE Copyright 2014 Wayne Morrison Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. AUTHOR Wayne Morrison, wayne@waynemorrison.com '''