Type less, smile more

How many times a week do you type /etc/init.d/blah start and then you realize you’re on HP-UX and it’s /sbin/init.d/blah Forget it.

# Turn on extended globbing and programmable completion
shopt -s extglob progcomp

export MYOS=$(uname -s)

if [[ "${MYOS}" = "Linux" || "${MYOS}" = "SunOS" ]] ; then
    INITDIR='/etc/init.d';
else
    INITDIR='/sbin/init.d';
fi
export INITDIR;

function RCCT () {.
    if [[ -z "${2}" ]] ; then.
        ls $INITDIR; 
    elif [[ "${1}" = "Start" ]] ; then
        cd / && $INITDIR/${2} start ; cd -;
    elif [[ "${1}" = "Restart" ]] ; then
        cd / && $INITDIR/${2} restart ; cd -;
    elif [[ "${1}" = "Stop" ]] ; then
        cd / && $INITDIR/${2} stop ; cd -;
    elif [[ "${1}" = "Reload" ]] ; then
        cd / && $INITDIR/${2} reload ; cd -;
    else
        echo "Something bad happened.";
    fi
}   

function _myservices() {
    local cur
    cur=${COMP_WORDS[COMP_CWORD]}
    COMPREPLY=( $( builtin echo $INITDIR/!(*.rpmsave|*.rpmorig|*.dpkg-old|*~|functions)) )  
    COMPREPLY=( $( compgen -W '${COMPREPLY[@]#@($INITDIR)/}' -- $cur) )
}

function Start () { RCCT ${FUNCNAME} ${*} ; };
function Restart () { RCCT ${FUNCNAME} ${*} ; };
function Reload () { RCCT ${FUNCNAME} ${*} ; };
function Stop () { RCCT ${FUNCNAME} ${*} ; };
complete -F _myservices Start Restart Reload Stop

You might be thinking that RCCT is pretty ugly and it is, but it’s complete-able, so it all shakes out. It also starts and stops services with / as the working directory which is a good idea if you’re dealing with Solaris or HP-UX.

Be yourself, even when you’re root

Ever run a command only to realize you’re not root but need to be? Of course you have. What if that command was long and painful to create? There’s no reason, Dude, to not have access to your bash history even after becoming root vi ‘su’.

function su () {
    local SUUSER=root
    local ORIGU=$USER
    local ORIGG=`groups | awk '{print $1}'`
    if [[ $# -gt 0 ]] ; then
        local char=`echo $1 | cut -c 1`
        if [[ "$char" == '-' ]] ; then
            /bin/su $*
            return $?
        else
            local SUUSER=$1
        fi
    fi
    #append recent history to the history file
    history -a
    /bin/su ${SUUSER} -c "env USER=${SUUSER} HOME=${HOME} ${SHELL}; \
          [ -f ${HOME}/.ICEauthority ] \
          && chown $ORIGU:$ORIGG ${HOME}/.ICEauthority ${HOME}/.viminfo"
    # Clear the history list by deleting all the entries.
    history -c
    # Read the contents of the history file and use them as the current history.
    history -r
}

This function honors the ‘su -‘ syntax in case you need it. If you have special permissions on your ~/.ICEauthority or ~/.viminfo, you’ll need to make adjustments obviously. Remember that the backslashes need to be the last characters on the line. They’re only there to make it more readable, so feel free to ditch them in favor of a longer line.

Now when you ‘su’ or even ‘su someotheruser’ you’ll get to keep your own history.

Bash SSH Happiness

So Dennis? (whose name doesn’t appear anywhere on his blog except for in the URL) wrote a cool bash script to create aliases for every host in your ~/.ssh/known_hosts file. I found some bugs/problems so I spiffed it up a bit. As the comments say, you should pipe the output to sort(1). I’ve tried to make the text as small as possible, but it won’t all display. I tested selecting it and even the parts that don’t display end up in my clipboard, so there ya go.

#!/bin/bash
shopt -s extglob
isint(){
    case $1 in
        ?([-+])+([0-9]) )
            return 0;;
        *) return 1;;
            esac
}

if [[ -d ~/.ssh ]]; then

    # Touch files we expect to exist
    if [[ ! -e ~/.ssh/config ]]; then touch ~/.ssh/config; fi
    if [[ ! -e ~/.ssh/known_hosts ]]; then touch ~/.ssh/known_hosts; fi

    # Parse ~/.ssh/known_hosts and ~/.ssh/config to find hosts
    for x in `sed -e 's/[, ].*//' ~/.ssh/known_hosts; awk '/^Host [^*?]+$/{print $2}' ~/.ssh/config`; do

        # Don’t override commands
        type "${x}" > /dev/null 2>&1 && continue

        # Remove the domainname
        y=${x%%.*}
        # you don't want IP addresses for aliases, trust me.
        isint $y && continue

        # If it's a short-name, move on
        #z=${x##*.}
        #[[  "${z}" == 'edu' ||  "${z}" == 'com' || "${z}" == 'net' ]] || continue
        # So the above is commented out because you'd be surprised at how much 
        # you rely on your search path. You should pipe the output of this script to
        # sort and your fqdn's will override your shorts.
        echo alias "${x}"=”ssh $x”

        if [[ "$y" != "$x" ]]; then
            if ! type $y > /dev/null 2>&1; then
            echo     alias $y=”ssh $x”
            fi
        fi
    done
fi

Dennis also mentions that you might want to add HashKnownHosts no to your ~/.ssh/config file. If you have some hashed keys in your file, you should remove them before running this script. Sadly I couldn’t find any way to programmatically convert a hashed file into a non-hashed file. But to make up for it, here’s a bonus alias!

# removes _exactly what you type_ from ~/.ssh/known_hosts
# meaning 'grapes' gets you the key for 'grapes' vs 'grapes.wrath.com'
alias forget="ssh-keygen -R"

Bash & Screen

You may be familiar with my tutorial on getting your ssh-agent to work inside screen. If not, have a look.

There’s always room for improvements! Here’s an excerpt from my current .bash_profile:

function Attach(){
    grabssh
    if [[ -z "${1}" ]] ; then
        local n=`screen -wipe | egrep -i 'attached|detached' | wc -l`
        if [[ "${n}" -gt 1 ]]; then
            check_screen
            return
        fi
    fi
    echo screen -d -R ${*}
    screen -d -R ${*}
}

check_screen () {
  # Look in the path?
    type screen > /dev/null 2>&1
    if [[ ${?} = 0 ]]; then
        tmp=0
        echo
        for scr in `screen -wipe | egrep -i 'attached|detached' | awk '{print $1"_"$2}'`
        do
            echo "Screen available: ${scr}"
            if [[ ${tmp} -eq 0 ]] ; then
                myscreen=${scr%_*}
            fi
            tmp=$(($tmp+1))
        done
        if [[ -n "${myscreen}" ]] ; then
            echo
            echo "Enter to attach to ${myscreen},"
            echo "'n' to move on,"
            echo "unique bits to attach elsewhere"
            read eon
            if [[ -z "${eon}" ]] ; then
                [[ -z "${myscreen}" ]] && return
                eon=${myscreen}
            fi
            if [[ "${eon}" != 'n' ]] ; then
                Attach ${eon}
            fi
        fi
    fi
}

[[ -n "$PS1" ]] && check_screen

If screen(s) exist, I’m presented with a list of them on login. I’m then able to hit return for a default, or type in which of them I wish to resume.

If I don’t want to resume any screen I can simply hit ‘n’ and go on about my day.

The Attach and check_screen functions are actually in a different file that is sourced by .bashrc and conditionally by .bash_login. This allows the functions to be used in non-login shells (in local terminal windows for example).