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:
A few neat tricks:
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:
- 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.
- 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.
- 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:
- 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)
- 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.
- my_reload() -- This unsets the $my_<PID> variable and reload the script picking up new settings.
- 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