Thursday, October 16, 2014

Script to pump output into a text file and open it in an editor

I use this little script a lot when I am running some command in bash (on Cygwin in Windows) and I want to capture the output into a text file for display into my favourite editor (like UltreEdit).

For example, the tree command (or a recursive ls) will have a lot of output if you run it on a directory with a few children and a few levels of nested folders. Because the output is usually too large to view nicely in a single screen of text on a console, I will want to view it in a text editor where I can manipulate it: search through it, cut/copy/paste contents and generally modify it.

Before I wrote the script this post is about, I would do this in the following way:

tree > /tmp/temp.txt; u /tmp/temp.txt

Easy enough, but tiresome to type over and over.

In the above snippet, the u command refers to one of my most used custom scripts to open a text file in my favourite editor (with a few bells and whistles). It could be replaced with the following in this case:

tree > /tmp/temp.txt; Uedit32.exe `cygpath -w -a "/tmp/temp.txt"` &

Or more simply, a *nix tool like vim or less (though less won't let you edit it).

tree > /tmp/temp.txt; vim /tmp/temp.txt
tree > /tmp/temp.txt; less /tmp/temp.txt

Now, I will do this:

tree | intoTempFile

The intoTempFile script will write the output into a timestamped temp file (C:\cygwin\tmp\temp_20141016_220342.txt) and open it in UltraEdit for me. Here is the script.

#!/bin/bash

# ------------------------------------------------------------------------------
# -- Into Temp File.
# ------------------------------------------------------------------------------
# Redirect piped input into a temp file and open it in UltraEdit.
#     Usage: someCommand | intoTempFile.sh [altFilename.txt] [-t]

# ------------------------------------------------------------------------------
# -- Variables for this script.
# ------------------------------------------------------------------------------
# Shortcut for the name of this file - for docs.
commandName=`echo $0 | sed 's|.*/||'`

# Output file.
tempFile=/tmp/temp_$(date +"%Y%m%d_%H%M%S").txt

# To tee or not to tee?
shouldUseTee=no

# ------------------------------------------------------------------------------
# -- Common functions for this script.
# ------------------------------------------------------------------------------

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Usage message.
#    PARAMETERS:  -
#       RETURNS:  -
# ==============================================================================
function usage() {
   echo "Usage: someCommand | $commandName [altFilename.txt] [-t]"
   echo "-t means use tee. Without it, nothing is output to console."
}

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Process all arguments to script. How to handle getopts args
#                 and operands at the same time:
#                    http://stackoverflow.com/a/21169366/257233
#    PARAMETERS:  -
#       RETURNS:  -
# ==============================================================================
function processArguments() {
   # Arg checks.. No more than two args
   if [ $# -gt 2 ] ; then
      echo "** Incorrect number of args specified **"
      usage
      exit 22
   fi

   # Loop to handle all parameters
   non_option_parameters=()
   while true; do
      # Process single letter args.
      while getopts "$OPTIONS" option; do
         if ! processSingleLetterArguments "$option"; then exit 9; fi
      done
      if ((OPTIND > $#)); then break; fi
      non_option_parameters+=(${!OPTIND})
      # Handle operand arg - file name
      if [ -z "$searchTerm" ] ; then
         tempFile="${!OPTIND}"
      fi
     ((OPTIND++))
   done

   # ---------------------------------------------------------------------------
   # Post argument processing - logic that must be applied once we know all args.
   # ---------------------------------------------------------------------------

}

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Process single letter args via getopts
#    PARAMETERS:  -
#       RETURNS:  -
# ==============================================================================
OPTIONS=":t"
function processSingleLetterArguments() {
   case "$1" in
      t ) shouldUseTee=yes;;
      \?)   usage "*** Invalid option to $commandName: -$OPTARG ***"; exit 10;;
      :)    usage "*** $commandName - option -$OPTARG requires an argument. ***"; exit 11;;
   esac
}

# ===  FUNCTION  ===============================================================
#   DESCRIPTION:  Output a single line
#                 Will query tee variable.
#    PARAMETERS:  1 - line to output
#       RETURNS:  -
# ==============================================================================
function outputLine() {
   if [ "$shouldUseTee" == "yes" ]  ; then
      echo "$1" 2>&1 | tee -a "${tempFile}"
   else
      echo "$1" >> "${tempFile}"
   fi
}


# ------------------------------------------------------------------------------
# -- Script logic.
# ------------------------------------------------------------------------------

# Process command line arguments.
processArguments "$@"

# Preserve indenting of source.
IFS='
'

touch  "${tempFile}"

outputLine "Starting output."
outputLine "----"
outputLine " "

while read -r x ; do
   outputLine "$x"
done

u.sh $tempFile

By default this will not show the output to the console. I can change that with the -t option as below - which will use tee to duplicate the output to console as well as a file. I don't usually use this though because it makes the script a lot slower.

tree | intoTempFile -t
Also, if I want to control the path to the text file being used, I can specify it like so.
tree | intoTempFile  /tmp/treeList.txt