Archive for the ‘/bin/bash’ Tag
Wearing both hats of developer and SysAdmin, I believe you shouldn’t work hard as a SysAdmin. It is for a reason that sometimes developers look at SysAdmins as inferior. It’s not because SysAdmins are really inferior, or has an easier job. I think it is mainly because a large portion of their workload could be automated. And if it can’t be automated (very little things), you should at least be able to do it quickly.
Obviously, we cannot buy time, but we can at least be efficient. In the following post I’ll introduce a few of my most useful aliases (or functions) I’d written and used in the last few years. Some of them are development related while the others could make you a happy SysAdmin.
You may find them childish – but trust me, sometimes the most childish alias is your best friend.
Jumping on the tree
Our subversion trunk really reminds me of a tree sometimes. The trunk is very thick, but it has many branches and eventually many leaves. While dealing with the leaves is rare, jumping on the branches is very common. Many times i have found myself typing a lot of ‘cd’ commands, where some are longer than others, repeatedly, just to get to a certain place. Here my stupid aliases come to help me.
lib takes me straight away to our libraries sub directory, where sim takes me to the simulators sub directory. Not to mention tr (shortcut for trunk), which takes me exactly to the sub directory where the sources are checked out. Oh, and pup which takes me to my puppet root, on my puppet master. Yes, I’m aware to the fact that you probably wonder now “Hey, is this guy going to teach me something new today? – Aliases are for babies!!”, I can identify. I didn’t come to teach you how to write aliases, I’m here to preach you to start using aliases. Ask yourself how many useful day-to-day aliases you really have defined. Do you have any at all? – Don’t be shy to answer no.
*nix jobs are diverse and heterogeneous, but let’s see if I can encourage you to write some useful aliases after all.
In case you need some ideas for aliases, run the following:
$ history | tr -s ' ' | cut -d' ' -f3- | sort | uniq -c | sort -n
Yes, this will show the count of the most recent commands you have used. Still not getting it? – OK, I’ll give you a hint, it should be similar to this:
$ alias recently_used_commands="history | tr -s ' ' | cut -d' ' -f3- | sort | uniq -c | sort -n"
If you did it – you’ve just kick-started your way to liberation. Enjoy.
As a dessert – my last childish alias:
$ alias rsrc='source ~/.bashrc'
Always useful if you want to re-source your .bashrc while working on some new aliases.
Two more things I must mention though:
- Enlarge your history size, I guess you can figure out alone how to do it.
- If you’re feeling generous – periodically collect the history files from your fellow team members (automatically of course, with another alias) and create aliases that will suit them too.
The serial SSHer
Our network is on 192.168.8.0/24. Many times I’ve found myself issuing commands like:
$ ssh root@192.168.8.1
$ ssh root@192.168.8.2
$ ssh root@192.168.8.3
Dozens of these in a single day. It was really frustrating. One day I decided to make an end to it:
# function for easier ssh
# $1 - network
# $2 - subnet
# $3 - host in subnet
_ssh_specific_network() {
local network=$1; shift
local subnet=$1; shift
local host=$1; shift
ssh root@$network.$subnet.$host
}
# easy ssh to 192.168.8.0/24
# $1 - host
_ssh_net_192_168_8() {
local host=$1; shift
_ssh_specific_network 192.168 8 $host
}
alias ssh8='_ssh_net_192_168_8'
Splendid, now I can run the following:
$ ssh8 1
Which is equal to:
$ ssh root@192.168.8.1
Childish, but extremely efficient. Do it also for other commands you would like to use, such as ping, telnet, rdesktop and many others.
The cop
Using KDE’s konsole? – I really like DCOP, let’s add some spice to the above function, we’ll rename the session name to the host we’ll ssh to, and then restore the session name back after logging out:
# returns session name
_konsole_get_session_name() {
# using dcop - obtain session name
dcop $KONSOLE_DCOP_SESSION sessionName
}
# renames a konsole session
# $1 - session name
_konsole_rename_session_name() {
local session_name=$1; shift
_konsole_store_session_name `_konsole_get_session_name`
dcop $KONSOLE_DCOP_SESSION renameSession "$session_name"
}
# store the current session name
_konsole_store_session_name() {
STORED_SESSION_NAME=`_konsole_get_session_name`
}
# restores session name
_konsole_restore_session_name() {
if [ x"$STORED_SESSION_NAME" != x ]; then
_konsole_rename_session_name "$STORED_SESSION_NAME"
fi
}
# function for easier ssh
# $1 - network
# $2 - subnet
# $3 - host in subnet
_ssh_specific_network() {
local network=$1; shift
local subnet=$1; shift
local host=$1; shift
# rename the konsole session name
_konsole_rename_session_name .$subnet.$host
ssh root@$network.$subnet.$host
# restore the konsole session name
_konsole_restore_session_name
}
Extend it as needed, this is only the tip of the iceberg! I can assure you that my aliases are much more complex than these.
Finders keepers
For the next one I’m not taking full credit – this one belongs to Uri Sivan – obviously one of the better developers I’ve met along the way.
Grepping cpp files is essential, many times I’ve found myself looking for a function reference on all of our cpp files.
The following usually does it:
$ find . -name "*.cpp" | xargs grep -H -r 'HomeCake'
But seriously, do I look like someone that likes to work hard?
# $* - string to grep
grepcpp() {
local grep_string="$*";
local filename=""
find . -name "*.cpp" -exec grep -l "$grep_string" "{}" ";" | while read filename; do
echo "=== $filename"
grep -C 3 --color=AUTO "$grep_string" "$filename"
echo ""
done
}
OK, let’s generalize it:
# grepping made easy, taken from the suri
# grepext - grep by extension
# $1 - extension of file
# $* - string to grep
_grepext() {
local extension=$1; shift
local grep_string="$*"
local filename=""
find . -name "*.${extension}" -exec grep -l "$grep_string" "{}" ";" | while read filename; do
echo "=== $filename"
grep -C 3 --color=AUTO "$grep_string" "$filename"
echo ""
done
}
# meta generate the grepext functions
declare -r GREPEXT_EXTENSIONS="h c cpp spec vpj sh php html js"
_meta_generate_grepext_functions() {
local tmp_grepext_functions=`mktemp`
local extension=$1; shift
for extension in $GREPEXT_EXTENSIONS; do
echo "grep$extension() {" >> $tmp_grepext_functions
echo ' local grep_string=$*' >> $tmp_grepext_functions
echo ' _grepext '"$extension"' "$grep_string"' >> $tmp_grepext_functions
echo "}" >> $tmp_grepext_functions
done
source $tmp_grepext_functions
rm -f $tmp_grepext_functions
}
After this, you have all of your C++/Bash/PHP/etc developers happy!
Time to showoff
My development environment is my theme park, here is my proof:
$ (env; declare -f; alias) | wc -l
2791
I encourage you to run this as well, if my line count is small and I’m bragging about something I shouldn’t – let me know!
I like Bash, I really like Bash. It’s simple, it’s neat, it’s quick, it’s many things. No one will convince me otherwise. It’s not for big things though, I know, but for most system administration tasks it will suffice.
The problem with Bash begins where incompetent SysAdmins start to use it. They will most likely treat it poorly and lamely.
Conventions are truly needed when writing in Bash. In our subversion we have ~30,000 lines of bash. And HELL NO! our product is definitely not a lame conglomerate of Bash scripts gluing many pieces of binaries together. Bash is there for everything that C++ (in our case) shouldn’t do, things like:
- Packaging and anything related to packaging (post scripts of packages for instance)
- SysV service infrastructure
- Backups
- Customization of the development environment
- Deployment infrastructure
Yes, so where have we been? – Oh, how do we manage ~30,000 lines of Bash without getting everything messed out. For this, we need 3 main things:
- Version control system (CVS, SVN, git, pick your kick)
- Competent people
- Coding conventions
Configuring a version control system is easy, espcially if you are a competent developer, I’ll skip to the 3rd one.
Conventions. Show me just one competent C/C++/Java/C# developer who would skip on using coding conventions and program like an ape. OK, I know, there might be some, but we both think the same thing about them. Scripting in Bash shouldn’t be an exception in that case. We should use strict scripting conventions on Bash in particular and on any other scripting language in general.
There’s nothing uglier and messier than a Bash script that is written without conventions (well, maybe Perl without conventions).
I’ll try in the following post to introduce my Bash scripting conventions and if you find them neat – feel free to adopt them. Up until today, sadly, I havn’t seen anyone suggesting any Bash scripting conventions.
Before starting any Bash script, I have the following skeleton :
#!/bin/bash
main() {
}
main "$@"
Call me crazy, but without my main() function I ain’t going anywhere. Now we can start writing some bash. Once you have your main() it means you’re going to write code ONLY inside functions. I don’t care it’s a scripting language – let’s be strict.
#!/bin/bash
# $1 - hostname
# $2 - destination directory
main() {
local hostname=$1; shift
local destination_directory=$1; shift
}
main "$@"
Need to receive any arguments in a function? – Do the following:
- Document the variables above the function
- Use shift after receiving each variable, and always use $1, it’s easier to later move the order of the variables
Notice that i’m using the local keyword to make sure the scope of the variable is only within its function. Be strict with variables. If for some reason you decide you need a global variable, it’s OK, but make sure it’s in capital letters and declared as needed:
# a read only variable
declare -r CONFIGURATION_FILE="/etc/named.conf"
# a read only integer variable
declare -i -r TIMEOUT=600
# inside a function
sample_function() {
local -i retries=0
}
You are getting the point – I’m not going to make it any easier for you. Adopt whatever you like and remember that being strict in Bash yields code in higher quality, not to mention better readability. Feeling like writing a sloppy script today? – Go home and come back tomorrow.
Following is a script written by the conventions I’m suggesting. This is a very simple script that simply displays a dialog and asks the user which host he would like to ping, then displays the result in a tailbox dialog. This is more or less how many of my scripts look like. I admit it took me a while to find a script which is not too long (under 100 lines) and can still represent some of the ideas I was mentioning.
I urge you to question my way and conventions and suggest some of your own, anyway, here it is:
#!/bin/bash
# dialog options (notice it's read only)
# notice as well it's in capital letters
declare -r DIALOG_OPTS="0 0 0"
declare -i -r OUTPUT_DIALOG_WIDTH=60
# ping timeout in seconds (using declare -i because it's an integer and -r because it's read only)
declare -i -r PING_TIMEOUT=5
# size of pings
declare -i -r DEFAULT_SIZE_OF_PINGS=56
# number of pings to perform
declare -i -r DEFAULT_NUMBER_OF_PINGS=1
# neatly pipes a command to a tailbox dialog
# here we can specify the parameters the function expects
# this is how i like to do it, perhaps there are smarter ways that may integrate better
# with doxygen or some other tools
# $1 - tailbox dialog height
# $2 - title
# $3 - command
pipe_command_to_tailbox_dialog() {
# parameters extraction, always using $1, with `shift` right afterwards
# in case you want to play with the order of parameters, just move the lines around
local -i height=5+$1; shift
local title="$1"; shift
local command="$1"; shift
# need a temporary file? - always use `mktemp`, please spare me the `/tmp/$$` stuff
# or other insecure alternative to temporary file creations, use only `mktemp`
local output_file=`mktemp`
# run in a subshell and with eval, so we can pass pipes and stuff...
# eval is a favorite of mine, it means you truely understand the Bash shell
# nevertheless - it is really needed in this case
(eval $command) >& $output_file &
# need to obtain a pid? - it's surely an integer, so use 'local -i'
local -i bg_job=$!
# ok, show the user the dialog
dialog --title "$title" --tailbox $output_file $height $OUTPUT_DIALOG_WIDTH
# TODO my lame way of checking if a process is running on linux, anyone has a better way??
# my way of specifying a 'TODO' is in the above line
if [ -d /proc/$bg_job ]; then
# if the process is stubborn, use 'kill -9'
kill $bg_job || kill -9 $bg_job >& /dev/null
fi
# wait for process to end itself
wait $bg_job
# not cleaning up your temporary files is similar to a memory leak in C++
rm -f $output_file
}
# pings a host with a nice dialog
ping_host_dialog() {
local ping_params_tmp=`mktemp`
# slice lines nicely, i have long commands, i'm not going even to mention
# line indentation - that goes without saying
# i like to use dialogs, it makes more people use your scripts
if dialog --ok-label "Submit" \
--form "Ping host" \
$DIALOG_OPTS \
"Address:" 1 1 "" 1 30 40 0 \
"Size of pings:" 2 1 "$DEFAULT_SIZE_OF_PINGS" 2 30 40 0 \
"Number of pings:" 3 1 "$DEFAULT_NUMBER_OF_PINGS" 3 30 40 0 2> $ping_params_tmp; then
# ping_params_tmp will be empty if the user aborted the dialog...
local address=`head -1 $ping_params_tmp | tail -1`
# yet again if you expect an integer, use 'local -i'
local -i size_of_pings=`head -2 $ping_params_tmp | tail -1`
local -i number_of_pings=`head -3 $ping_params_tmp | tail -1`
fi
rm -f $ping_params_tmp
# this is my standard way of checking if a variable is empty
# may not be the prettiest way, but it surely catches the eye...
if [ x"$address" != x ]; then
pipe_command_to_tailbox_dialog 15 "Pinging host \"$address\"" "ping -c $number_of_pings -s $size_of_pings -W $PING_TIMEOUT \"$address\""
fi
}
# main function, can't live without it
main() {
ping_host_dialog
}
# although there are no parameters passed in this script, i still pass $* to main, as a habit
#main $*
# after Uri's comment, I'm fixing the following and calling "$@" instead
main "$@"
I don’t want this post to be too long (it is already quite long), but I think you got the idea. I still have some conventions I did not introduce here. In case you liked what you’ve seen – do not hesitate to contact me so I can provide you with a document describing all of my Bash scripting conventions.