I updated Advancing in the Bash Shell a bit. A new link at the bottom, some very small wording corrections and a syntax highlighter plugin to make the examples a bit easier (I hope!) to read.
Tag: bash
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).