Tuesday, November 13, 2012

my monster .shrc lately

It's been while since I posted my monster .shrc file. There were a few bug fixes over time and some improvements that I had to make recently so I thought I'd post the latest version. You can find it on my fossil server at http://fossil.secution.com/u/gcw/dotfiles/dir?ci=tip. Occasionally I remember to push it up to github ( https://github.com/gcw/dotfiles/blob/master/dot_shrc ). The file is now just about 400 ones long.

There are customizations for 3 advanced shells (KSH, ZSH (4.3.9+), MKSH) generics for a standard posix compliant shells (like ash, dash, posh, etc) and even stuff to make BASH more palatable when I'm stuck on a linux box.

I've included it below just for kicks with angry fruit-salad colors so you can get an idea of what's in it. If you actually want to download and make use of it, please do your self a favor and use one of the links above. Copying and pasting from here will most assuredly produce unexpected behavior. Some of the differences from when I posted about it before is that now my_pathadd() can handle paths with spaces in them. This was pretty key to me getting the titanium alias in place. I've compromised on my own box to allow the use of ls, sed, and head.  I am loathe to call external programs from shell scripts as they tend to add process overhead. In this case it's for one section where I need to create an alias that I use for doing mobile development with Appcelerator's Titanium framework. I left it in to illustrate for the masses how I get it done but most people can completely remove that whole subsection beginning with line #232 going through #252. The previous post did have a call to AWK to help resolve the currently employed shell. That has long since been modified to stay 'native'.

The over arching goal here is to give myself a shell that works the way I expect it to no mater the constraints of the environment I'm in. Since I work on many boxes and different operating systems, it's nice to know that I won't need to fiddle with too much just to get my work done. Hopefully others will derive some benefit from it being made public.

The core principles were:

  1. Keep it standard -- There are many rings that could have been done in a more elegant way if I'd been willing to compromise and use advanced shell features. This was rejected and everything, to the best of my knowledge, conforms to the 2008 opengroup/POSIX standard. That means it should work on every posix complaint shell out there.
  2. Don't needlessly call out to external programs -- There are parts of the code where it would have been easier to get something done by calling grep, sed, etc but I chose to keep it, as much as possible, shell code and not rely on external programs to do what the shell (as defined by the published POSIX specifications) can handle internally. For example in my_pathmatch() (beginning at line #56) I use a case statement instead of calling out to grep or sed.  Again, in my_pathadd() I use eval instead of calling sed or awk to create the string lists needed.
  3. Keep it performant -- Despite being 400 lines long it still performs pretty well… ok honestly it does well with zsh, ash, mksh, and just flys with KSH93. Unfortunately bash is dog slow and it's somewhat noticeable on my 5 and 7 year old machines when I do testing with bash. As I regularly avoid bash this isn't an issue for me. If you have enhancements/tweaks please let me know. I'd be happy to incorporate things that fall in line with #1 and #2. 


A few neat tricks:

  1. my_pathadd <PATH_VARIABLE_NAME> PATH1 PATH2 PATH3 -- will first check that the supplied paths exist and that they are not already in the path variable named. Once the checks are done named path variable gets the valid paths added. (e.g. my_pathadd PATH ~/.bin ~/.rvm/bin)
  2. At line 22 the simple (but possibly confusing) use of eval -- This is used to set a variable named for the PID of the currently running shell. This is used by the script to make sure that it doesn't loop over itself by setting $my_<PID> to the string "processed". You can use it in your scripts to (provided you know how to get at it with eval.  An example is at line #18 where we check to make sure the file hasn't been processed by this shell instance. 
  3. my_reload() -- This unsets the $my_<PID> variable and reload the script picking up new settings. 
  4. At line 91 the while loop -- The while loop is there because the for loop (commented out at line #92) ends up mangling the contents of the string-list in some shells. The only consistent way to get proper quoting/escaping of the elements of the list (that I could come up with) was to do a while loop calling shift and pulling ${1}. Not really a 'neat trick' just a workaround that seems to work (so far). 


-->
  1 #Title: N/A
  2 #Author:    G. Clifford Williams
  3 #Purpose:   used as a .shrc or .profile this script initializes interactive
  4 #           shell sessions with the specified configuration determined by
  5 #           Operating System and/or shell implementation. In particular this
  6 #           script is for shells that implement the POSIX
  7 #           This Part
  8 
  9 
 10 #-----------------------------------------------------------------------------#
 11 #------------------------------------INIT-------------------------------------#
 12 #-----------------------------------------------------------------------------#
 13     #This section checks to see whether the file has been read already. If so
 14     #it exits (using the return command) with a nice message. It works by 
 15     #setting a variable named for the pid of the current shell ($$). If the 
 16     #variable is not empty the file has been processed before. This makes it 
 17     # more suitable to use one file for both the .profile and .shrc
 18 [ "$(eval "echo \${my_${$}}")" = "processed" ] && \
 19         { #echo "already read for my_${$}"
 20             return 1;}
 21 
 22 eval "my_${$}=processed" #set marker to indicate we've processed this file
 23 
 24 #-----------------------------------------------------------------------------#
 25 #-------------------------------RUN MODE CHECK--------------------------------#
 26 #-----------------------------------------------------------------------------#
 27 case "$-" in
 28     #This section checks for the 'interactive mode' flag in the '$-' variable
 29     #By checking the my_INTERACTIVE variable you can set chunks of code to 
 30     #be conditional on the manner in which the shell was invoked
 31     *i* )
 32             my_INTERACTIVE="yes"
 33             ;;
 34     * )
 35             my_INTERACTIVE="no"
 36             ;;
 37 esac
 38 
 39 
 40 #-----------------------------------------------------------------------------#
 41 #------------------------------------FUNCTIONS--------------------------------#
 42 #-----------------------------------------------------------------------------#
 43 my_lecho(){
 44     [ -n my_SILENT ] || echo "$(date +%H:%M:%S)|$@"
 45 }
 46 
 47 my_pathmatch_cleanup(){
 48     #clean up some sloppy global variables. I should really clean these up
 49     unset mPMatchDel
 50     unset mPMatchVar
 51     unset mPMatchString
 52     #zsh specific clean up
 53     [ "${reset_wordsplit:=no}" = "yes" ] && setopt noshwordsplit
 54 }
 55 
 56 my_pathmatch(){
 57     mPMatchVar=${1}        #Variable to check
 58     mPMatchString=${2:?}   #string to look for
 59     mPMatchDel=${3:-":"}   #optional delimiter (use ';' for LUA*)
 60 
 61     case ${mPMatchVar} in
 62         ${mPMatchString}|\
 63         ${mPMatchString}${mPMatchDel}*|\
 64         *${mPMatchDel}${mPMatchString}${mPMatchDel}*|\
 65         *${mPMatchDel}${mPMatchString})
 66             echo "rejecting (${mPMatchString}) because it's in there already"
 67             { my_pathmatch_cleanup; return 0 ;}
 68             ;;
 69     esac
 70     my_pathmatch_cleanup #clean up
 71     return 1 #return 1 if we've gotten this far (indicates no match above)
 72 }
 73 
 74 my_pathadd(){
 75     #This is a function to add path elements to a path variable. $1 is the 
 76     #name of the variable to append to (note: use PATHNAME, not $PATHNAME)
 77     my_PATHVAR=$1   #take the first parameter as PATHVAR
 78     shift           #remove $1 and shift the other params down
 79     case $my_PATHVAR in
 80         LUA*)
 81             #If we're modifying a Lua Path we set the separator to ';' instead
 82             #of the standard ':'
 83             my_OFS=";"
 84             ;;
 85         *)
 86             my_OFS=":"
 87             ;;
 88     esac
 89 
 90     [ -n "${1:?"USAGE: my_pathadd <VARIABLE> <PTH1> [<PTH2>] ..."}" ]
 91     while [ ${#} -gt 0 ] ; do
 92     #for PATH_add in ${@:?"USAGE: my_pathadd <VARIABLE> <PTH1> [<PTH2>]..."};do 
 93         PATH_add=${1}
 94         if [ -d "${PATH_add}" ] ; then #if the path is an existing dir
 95             if eval "[ -n \"\${$my_PATHVAR}\" ]" ; then #if the variable exists
 96                 #call my_pathmatch <VARIABLE> <PATH> <FS>. If we get 0 as a 
 97                 #return code (success) we know the PATH is already in 
 98                 #<VARIABLE>. If we get anything else we add PATH to <VARIABLE>
 99                 eval "my_pathmatch \"\${$my_PATHVAR}\" \"${PATH_add}\" \\${my_OFS}" ||\
100                 eval "$my_PATHVAR=\"\${$my_PATHVAR}${my_OFS}${PATH_add}\""
101             else
102                 #If PATHVAR doesn't already exist we initialize it to the first
103                 #PATH parameter passed ($2)
104                 eval "$my_PATHVAR=\"$PATH_add\""
105             fi
106         fi
107         shift
108     done
109     eval "export $my_PATHVAR"
110     unset my_PATHVAR
111     unset PATH_add
112 }
113 
114 my_cleanpath(){
115     #function to set a very basic PATH
116     PATH=/bin:/usr/bin:/sbin:/usr/sbin
117 }
118 
119 my_reload(){
120     #Quick function to unset my_<PID>; and reload ENV file. This is the way
121     #to refresh your environment since, by default, just sourcing it after
122     #initialization will not work.
123     unset my_${$}
124     #[ -n ${BASH_ENV} ] && . ${BASH_ENV:"$ENV"} || . ${ENV}
125     my_loadfile=${BASH_ENV:-"${ENV}"}
126     [ -n ${my_loadfile} ] && . ${my_loadfile}
127 }
128 
129 my_getshell(){
130     #Biased test to figure out what shell we're running. We check for KSH first
131     #Some shells that are not true KSH (PDKSH|OKSH|MKSH) may lie here. If this
132     #test does not work for your needs please modify it. It would be nice if
133     #you sent your modification along so for the benefit of others.
134     if [ "${KSH_VERSION}X" != "X" ] ; then
135         echo "KSH_VERSION:${KSH_VERSION}"
136     elif [ "${ZSH_VERSION}X" != "X" ] ; then
137         echo "ZSH_VERSION:${ZSH_VERSION}"
138     elif [ "${BASH_VERSION}X" != "X" ] ; then
139         echo "BASH_VERSION:${BASH_VERSION}"
140     elif ([ "${.sh.version}X" = "Version M 1993-12-28 s+X" ]) 2>/dev/null; then
141         #The above is a test for the specific version of KSH that comes with
142         #OS X. Standard versions of KSH can be detected with the $KSH_VERSION}
143         #check at the top of this function. You will need to update the string
144         #if Apple ever gets around to upgrading their included KSH. 
145         #Note 1: The surrounding ()s are used to catch errors from shells that 
146         #complain about 'bad substitution'. 
147         #Note 2: This test is expensive so we only execute it if all else fails
148         echo "KSH_VERSION:${.sh.version}"
149     else
150         echo "UNKNOWN"
151     fi
152 }
153 
154 #-----------------------------------------------------------------------------#
155 #-------------------Universal/Generic Settings--------------------------------#
156 #-----------------------------------------------------------------------------#
157 
158 #------Get our SHELL-----#
159 [ -n "${my_SHELL}" ] || my_SHELL=$(my_getshell)
160 
161 #-----Get our OS-----#
162 my_OS=$(uname)
163 
164 #----Get our username----#
165 my_USERNAME=$(id -un 2> /dev/null || id -u)
166 
167 #------Set EDITOR(s)-----#
168 if { which vim 2> /dev/null  1> /dev/null ;}; then
169     EDITOR=vim
170 else
171     EDITOR=vi
172 fi
173 FCEDIT=$EDITOR
174 VISUAL=$EDITOR
175 HISTEDIT=$EDITOR
176 TERM="xterm-256color"
177 export EDITOR
178 export FCEDIT
179 export HISTEDIT
180 export TERM
181 #------Set PAGER-----#
182 if { which less 2> /dev/null 1> /dev/null;}; then
183     PAGER=less
184 else
185     PAGER=more
186 fi
187 export PAGER
188 
189 #------Useful bits of info-----#
190 my_FULLHOSTNAME=$(hostname)
191 my_HOST=${my_FULLHOSTNAME%%.*}
192 my_DOMAIN=${my_FULLHOSTNAME#*.}
193 my_NEWLINE="
194 "
195 #The above two lines are a cheap hack to set a litteral new line character
196 #echo and print can't be used in a reliable fashion to do this across 
197 #environments. The above does not work with all shells, but it should not
198 #cause any problems. If you have a better (POSIX compliant) idea on how to
199 #do this, please let me know.
200 
201 
202 case ${my_OS:-unset} in
203     Darwin )
204         #-------OS X Specifics-------#
205         unset PATH
206         unset MANPATH
207         #added for HomeBrew (first priority)
208         my_pathadd PATH /usr/local/bin /usr/local/sbin
209         my_pathadd MANPATH /usr/local/man
210 
211         #added for macports (third priority)
212         my_pathadd PATH /opt/local/bin /opt/local/sbin
213         my_pathadd MANPATH /opt/local/man
214 
215         #regular PATH(s)
216         my_pathadd PATH /usr/bin /bin /usr/sbin /sbin /usr/local/bin
217         my_pathadd PATH /usr/local/sbin /usr/X11/bin
218         my_pathadd MANPATH /usr/man /usr/X11/man /usr/X11/share/man
219 
220         #added for pkgsrc   (second priority)
221         my_pathadd PATH /usr/local/pkg/bin /usr/local/pkg/sbin
222         my_pathadd MANPATH /usr/local/pkg/man
223 
224         #added for node.js  (homebrew install) 
225         my_pathadd NODE_PATH /usr/local/lib/node_modules
226 
227         LC_CTYPE=en_US.UTF-8
228         export LC_CTYPE
229         OFFLINE=1
230         export OFFLINE
231 
232         unset ti_path
233         #Titanium CLI support on OS X
234         for try_path in  \
235             "${HOME}/Library/Application Support/Titanium/mobilesdk/osx" \
236             "/Library/Application Support/Titanium/mobilesdk/osx" ; do
237             if [ ${#ti_path} -eq 0 ] ; then
238                 my_pathadd ti_path "${try_path}"
239             fi
240             unset try_path
241         done
242         if [ -n "${ti_path}" ] ; then
243             alias titanium="echo 'not yet poppin'"
244             unset ti_version
245             unalias titanium
246             ti_left="${ti_path%% *}"
247             ti_right="${ti_path##* }"
248             ti_version=$(ls -t "${ti_path}"|head -1)
249             alias titanium="$(echo ${ti_path}/${ti_version}/titanium.py |\
250                                 sed 's/ /\\ /g')"
251             unset ti_left
252             unset ti_right
253         fi
254         ;;
255     FreeBSD )
256         #-------FreeBSD Specifics-------#
257         BLOCKSIZE=K
258         export BLOCKSIZE
259         my_cleanpath
260         my_pathadd PATH /usr/local/bin /usr/local/sbin
261         ;;
262     Linux )
263         #-------Linux Specifics-------#
264         my_cleanpath
265         my_pathadd PATH /usr/local/bin /usr/local/sbin
266         #get rid of that annoying and stupid 'colorls' crap
267         alias ls='/bin/ls' #set it just in case it's not yet set
268         unalias ls #unset it now
269         #alias titanium=$HOME/.titanium/mobilesdk/linux/${ti_version}>/titanium.py
270         ;;
271     CYGWIN_NT* )
272         #-------CygWin Specifics-------#
273         CYGWIN=tty
274         export CYGWIN
275         zstyle :compinstall filename '/cygdrive/c/.zshrc'
276         ;;
277 esac
278 
279 #----------ALL SHELLS SETTINGS-----------#
280 HISTSIZE=2500
281 
282 case ${my_SHELL:-unset} in
283     ZSH* )
284         #--------Z SHELL--------#
285         my_lecho "initializing ZSH"
286         # Lines configured by zsh-newuser-install
287         HISTFILE=~/.zsh_history
288         SAVEHIST=1000000
289         #don't overwrite history file
290         setopt appendhistory
291         #get rid of dupes in the history file
292         setopt hist_save_no_dups
293         #try good file locking for history file
294         setopt hist_fcntl_lock
295         #don't show dupes in history search
296         setopt hist_find_no_dups
297         #don't store the history/fc command in the history file
298         setopt hist_no_store
299         #write to the history file incrementally
300         setopt share_history
301         #allow short forms of various control structures
302         setopt short_loops
303         #list options on ambiguous completion
304         setopt autolist
305         bindkey -v
306         autoload -Uz compinit
307         compinit
308         # End of lines added by compinstall
309         PS1="[%n@%m:%/>${my_NEWLINE}%# "
310         ENV=${HOME}/.zshrc
311         export ENV
312         SHELL=$(which zsh) #Needed for screen on GNU systems (bleh)
313         export SHELL
314         [[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" # Load RVM into a shell session *as a function*
315         [[ -s "$HOME/.pythonbrew/etc/bashrc" ]] && source "$HOME/.pythonbrew/etc/bashrc"
316         ;;
317     BASH* )
318         #--------Bourne Again SHELL--------#
319         my_lecho "initializing BASH"
320         set -o vi #vi mode editing
321         set -b #immediate background job reporting
322         set -B #brace expansion
323         BASH_ENV=${HOME}/.bashrc
324         export BASH_ENV
325         #source the BASH_ENV if it's readable
326         [ -r ${BASH_ENV} ] && . ${BASH_ENV}
327         HISTFILE=${HOME}/.bash_history
328         HISTFILESIZE=100000
329         #Don't put Dupes in history file 
330         export HISTCONTROL=ignoredups
331         #Append to history file (don't overwrite)
332         shopt -s histappend
333         #update LINES & COLS when window size changes
334         shopt -s checkwinsize
335         #look for bash completion files
336         [ -f /etc/bash_completion ] && . /etc/bash_completion
337         [ -f /usr/local/etc/bash_completion ] &&\
338             . /usr/local/etc/bash_completion
339         [ -f /opt/local/etc/bash_completion ] &&\
340             . /opt/local/etc/bash_completion
341         PS1="[\u@\h:\w>\n\$ "
342         #[[ -s "/Users/gcw/.rvm/scripts/rvm" ]] && . "/Users/gcw/.rvm/scripts/rvm" # Load RVM into a shell session *as a function*
343         [[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" # Load RVM into a shell session *as a function*
344         [[ -s "$HOME/.pythonbrew/etc/bashrc" ]] && source "$HOME/.pythonbrew/etc/bashrc"
345         ;;
346     *MIRBSD\ KSH*)
347         set -o vi #vi mode
348         set -o vi-tabcomplete #tab completion 
349         ENV=${HOME}/.mkshrc
350         HISTFILE=${HOME}/.mksh_history
351         HISTFILESIZE=100000
352         PS1='$(whoami)@$(hostname -s):$(pwd)> '
353         case $(id -u) in
354             0 ) PS1="${PS1}${my_NEWLINE}:# ";;
355             * ) PS1="${PS1}${my_NEWLINE}:$ ";;
356         esac
357         ;;
358     KSH* )
359         #--------Korn SHELL--------#
360         my_lecho "initializing KSH (or something pretending to be it)"
361         set -o vi #vi mode 
362         set -o viraw #for real KSH
363         set -o bgnice #nice background processes
364         set -b #immediate background job reporting
365         ENV=${HOME}/.kshrc
366         export ENV
367         HISTFILE=${HOME}/.ksh_history
368         HISTFILESIZE=100000
369         PS1='$(whoami)@$(hostname -s):$(pwd)> '
370         case $(id -u) in
371             0 ) PS1="${PS1}${my_NEWLINE}# ";;
372             * ) PS1="${PS1}${my_NEWLINE}$ ";;
373         esac
374         ;;
375     * )
376         #--------GENERIC SHELL--------#
377         my_lecho "initializing unknown shell"
378         set -o vi
379         HISTFILE=${HOME}/.sh_history
380         HISTFILESIZE=100000
381         ENV=${HOME}/.shrc
382         export ENV
383         #PS1='$(whoami)@$(hostname -s):$(pwd)>'
384         PS1="$my_USERNAME@$my_HOST: "
385         case $(id -u) in
386             0 ) PS1="${PS1}${my_NEWLINE}# ";;
387             * ) PS1="${PS1}${my_NEWLINE}$ ";;
388         esac
389         ;;
390 esac
391 
392 #-------After all is said and done-------#
393 my_pathadd PATH ~/bin ~/scripts ~/.bin
394 
395 #-------Domain specific RC-------#
396 [ -r ${HOME}/.shrc_${my_DOMAIN} ]  && .  ${HOME}/.shrc_${my_DOMAIN}
397 #-------HOST specific RC-------#
398 [ -r ${HOME}/.shrc_${my_HOST} ]  && .  ${HOME}/.shrc_${my_HOST}
399 #-------LOCAL RC (always run if present)-------#
400 [ -r ${HOME}/.shrc_local ]  && .  ${HOME}/.shrc_local
401 
402 #PATH=$PATH:$HOME/.rvm/bin # Add RVM to PATH for scripting


No comments:

Post a Comment