#!/bin/bash # copyright 2007 Gilbert Ashley # minimal implementation similar to the 'cut' command # show program usage show_usage() { echo echo ${0##/*}" Usage:" echo " Cut FIELDS from STRING using separator delimiter SEP" echo "${0##/} -[fFIELDS|f FIELDS|--fields=] -[dSEP|d SEP] STRING" echo "Using --output-delimiter= replaces the delimiter in the output." echo "Fields can be given using one of these forms: single field '2'" echo "open range '4-', closed range '2-5', or comma-separated list '2,3,4'" exit } # Minimum number of arguments needed by this program MINARGS=2 # show usage if '-h' or '--help' is the first argument or no argument is given case $1 in ""|"-h"|"--help") show_usage ;; esac # get the number of command-line arguments given ARGC=$# # check to make sure enough arguments were given or exit if [[ $ARGC -lt $MINARGS ]] ; then echo "Too few arguments given (Minimum:$MINARGS)" echo show_usage fi # self-sorting argument types LongEquals, ShortSingle, ShortSplit, and ShortMulti # process command-line arguments for WORD in "$@" ; do case $WORD in -*) true ; case $WORD in --fields=*) [[ $DEBUG ]] && echo "Long FIELD Option using '='" # FIELDS=${WORD##*=} #FIELDS=${WORD%*=} FIELDS=${WORD:9} shift ;; -f?) [[ $DEBUG ]] && echo "Short single FIELD Option" #FIELDS=${WORD##*f} FIELDS=${WORD:2:1} shift ;; -f) [[ $DEBUG ]] && echo "Short split FIELD Option" if [[ ${2:0:1} != "-" ]] ; then FIELDS=$2 shift 2 else echo "Missing argument" show_usage fi ;; -f*) [[ $DEBUG ]] && echo "Short FIELD Option range" #FIELDS=${WORD##*f} FIELDS=${WORD:2} shift ;; -d?) [[ $DEBUG ]] && echo "Short single DEL Option" #SEP=${WORD##*d} SEP=${WORD:2:1} shift ;; -d) [[ $DEBUG ]] && echo "Short split FIELD Option" if [[ ${2:0:1} != "-" ]] ; then SEP=$2 shift 2 else echo "Missing argument" show_usage fi ;; -d*) [[ $DEBUG ]] && echo "Short-short DEL Option" # SEP=${WORD##*d} SEP=${WORD:2} shift ;; --output-delimiter=*) [[ $DEBUG ]] && echo "Long NEW_SEP Option" #NEW_SEP=${WORD#*=} #NEW_SEP=${WORD%%*=} NEW_SEP=${WORD:19} shift ;; -*) [[ $DEBUG ]] && echo "Unrecognized Short Option" echo "Unrecognized argument" ;; esac ;; esac done # set the NEW_SEP to SEP unless --output-delimiter was given ! [[ $NEW_SEP ]] && NEW_SEP=$SEP ### functions ##### ######################## function _cat() { ERROR=0 # error (0=no, 1=yes) LINE= # Line read from file while [ $# -gt 0 ] do if [ ! -r "$1" ]; then echo "Cannot find file $1" 1>&2 ERROR=1 else IFS= while read LINE do echo "$LINE" done <"$1" fi shift done exit $ERROR } # function _freq counts the number of matches # of PATTERN in PARSESTRING and returns FREQ # example usage: _freq $PATTERN $PARSESTRING function _freq() { FREQ=0 ! [[ $PATTERN ]] && PATTERN=$1 ! [[ $PARSESTRING ]] && PARSESTRING=$2 while [[ $PARSESTRING != "" ]] ; do case $PARSESTRING in *$PATTERN*) (( FREQ++ )) ; PARSESTRING=${PARSESTRING#*${PATTERN}} ;; *) PARSESTRING="" ;; esac done echo $FREQ } # _get_one_field returns the contents of a FIELD in STRING # fields are determined by a dividing separator SEP # STRING, FIELD and SEP must be supplied to this function function _get_one_field() { if [[ $FIELD = 1 ]] ; then # output the contents to the left of the first SEP OUTPUT=${STRING%%${SEP}*} else # Chop off the first match and cheat on the count STUB=${STRING#*$SEP} COUNT=1 # skip over any matches before the requested one while [[ $COUNT -lt $(( $FIELD - 1 )) ]] ; do # ROF Chop off leading FIELD and/or SEP STUB=${STUB#*$SEP} (( COUNT++ )) done # LOF Chop off trailing SEP and/or rest of STUB OUTPUT=${STUB%%${SEP}*} fi echo $OUTPUT } # _process_one_line returns the contents of the FIELD # specified by the current field LIST function _process_one_line() { # process the prepared LIST of FIELDS and create LINE_OUTPUT # read first character of FIELDS variable HEAD=${LIST:0:1} # increment the LIST by chopping the first 2 digits LIST=${LIST:2} # set the FIELD to the HEAD FIELD=$HEAD # read the contents of the START or HEAD FIELD OUTPUT="$(_get_one_field)" # start the OUTPUT_STACK with the first OUTPUT OUTPUT_STACK="$OUTPUT" # read the *rest* of the LIST of fields while [[ $LIST != "" ]] ; do # readfirst character to get next FIELD number FIELD=${LIST:0:1} # the OUTPUT of the current FIELD OUTPUT="$(_get_one_field)" # add the OUTPUT to OUTPUT_STACK with a NEW_SEP between OUTPUT_STACK=$OUTPUT_STACK$NEW_SEP$OUTPUT # increment LIST by 2 CHARS LIST=${LIST:2} done # this the final ouput for each line of input LINE_OUTPUT=$OUTPUT_STACK echo $LINE_OUTPUT } # the command-line FIELDS variable takes one of these forms: # single field '2', open range '4-', closed range '2-5', or comma-separated list '2,3,4' # We examine the FIELDS variable with if's and convert everything to a LIST # the first character is always the START or HEAD # if the second character is a '-' then this is a range like: 2- or 3-5 if [[ ${FIELDS:1:1} = "-" ]] ; then # if the third character is null, range goes to end of STRING if [[ ${FIELDS:2:1} = "" ]] ; then # this is an open START to END range like: 2- START=${FIELDS:0:1} # _freq counts the number of SEP's in STRING FINISH=$(_freq $SEP $STRING) # there are always one more TOTAL_FIELDS then SEP's FINISH=$(( FINISH + 1 )) else # this is a closed range like: 3-5 START=${FIELDS:0:1} FINISH=${FIELDS:2:1} fi # translate the RANGE values to a comma-separated LIST LIST="$START," COUNT=$START while [[ $COUNT -lt $(( $FINISH - 1 )) ]] ; do (( COUNT++ )) LIST=$LIST$COUNT"," done LIST=$LIST$FINISH elif [[ ${FIELDS:1:1} = "," ]] ; then # this is already a comma-separated list # TODO? support a list like this:2,3,5- LIST=$FIELDS elif [[ ${FIELDS:1:1} = "" ]] ; then # single-character argument given LIST=${FIELDS:0:1} fi # save the FULL_LIST FULL_LIST=$LIST if [[ $# -gt 0 ]] ; then ERROR=0 # Has there been an error (0=no, 1=yes) LINE= # Line read from file while [ $# -gt 0 ] do if [ ! -r "$1" ]; then echo "Cannot find file $1" 1>&2 ERROR=1 else IFS= while read LINE do #echo "$LINE" STRING="$LINE" _process_one_line # reset the FIELD LIST LIST=$FULL_LIST ; # then go back and read more input if needed (next LINE) done <"$1" shift fi # reset the FIELD LIST LIST=$FULL_LIST ; # then go back and read more input if needed (next file) done shift exit $ERROR else while read LINE ; do # be sure every line has a ';' ending for this to work #cat ./Notes | while read LINE ; do #cat ./Notes | while read LINE ; do echo $LINE ; STRING=$LINE ; _process_one_line ; LIST=$FULL_LIST ; done STRING=$LINE ; _process_one_line ; # done with while read? # reset the FIELD LIST LIST=$FULL_LIST ; # then go back and read more input if needed done fi exit 0