Integer conversions in bash

Since version 2, bash support a single aritmethic operations. Altough bash is not a mathematical shell (use bc instead), you can perform certain conversions using the bash arithmetic logic.

For example you can remove the left zeroes in a decimal number without require any external utility or print formats, let’s suppose that you want to strip zeroes from the number 007, which is stored in bond variable.

$ echo $bond
007
$ let nozeros=10#$bond
$ echo $nozeros
7

In many forums and mailing list, people need to use ugly sed expressions, or awk invokation, but (with bash) it’s just simply πŸ™‚

Using the same trick, you can perform a base conversions, for example:

$ let i=0x10
$ echo $i
16
$ let i=2#10000
$ echo $i
16

Or create an easy number checking:

$ is_decimal () { let i=10#$1 2>/dev/null; }
$ is_decimal 'a' || echo Nop
Nop
$ is_decimal 56 && echo 'Yep'
Yep

Enjoy!

Update dot files with git

For a years I was using a custom created scripts to keep my dot files updated. I had a local repository in bazaar and a script which check differences between home dot files and files stored in the repository. This solutions works fine for years, but now I want to do some changes…

The first one is moving my dot files to git (and probably pushed them to github), and the second one is to create a hook for git to update my dot files. I known that there are a lot of similar solutions, one more complex, other more easy, but this is mine πŸ™‚

So, I created a post-commit hook script for git, which perform the modifications that I need. Now I just only do this steps:

1. Create a new git repo:

mkdir mydots_repo
cd my_dots_repo && git init

2. Put the hook:

wget -O .git/hooks/pre-commit  http://2tu.us/2scm
chmod 755 .git/hooks/pre-commit

Or just put this content to pre-commit hook:

#! /bin/bash
# (c) 2010 Andres J. Diaz <[email protected]>
# A hook to git-commit(1) to update the home dot files link using this
# repository as based.
#
# To enable this hook, rename this file to "post-commit".

for dot in $PWD/*; do
 home_dot="$HOME/.${dot##*/}"

 if [ -L "${home_dot}" ]; then
 if [ "${home_dot}" -ef "$dot" ]; then
 echo "[skip] ${home_dot}: is already updated"
 else
 rm -f "${home_dot}" && \
 ln -s "$dot" "${home_dot}" && \
 echo "[done] updated link: ${home_dot}"
 fi
 else
 if [ -r "${home_dot}" ]; then
 echo "[keep] ${home_dot}: is regular file"
 else
 ln -s "$dot" "${home_dot}" && \
 echo "[done] updated link: ${home_dot}"
 fi 
 fi
done
true

3. Copy old files:

cp ~/old/bzr_repo/* .
git add *
 

4. Commit and recreate links:

git commit -a -m'initial import'

And it’s works πŸ™‚

bash ini parser

In some situations i like to use INI files as configuration files, as python do. But bash do not provide a parser for these files, obviously you can use a awk code or a couple of sed calls, but if you are bash-priest and do not want to use nothing more, then you can try the following obscure code:

Update:Added support spaces between keys and values, described in comment #364

Update:Added a writer function, described in comment #370 with examples.

Update:Fix bug when values or keys contains a ‘#’ sign, as described in comment #397. Now only comments at the beginning of line are allowed.

Update:Fix bug in section evaluation when a file which match section expression exists in cwd, as described in comment #436. Fixed also a bug related with
parser when brackets are escaped (read #437.

cfg_parser ()
{
	ini="$(<$1)"                # read the file
	ini="${ini//[/\[}"          # escape [
	ini="${ini//]/\]}"          # escape ]
	IFS=$'\n' && ini=( ${ini} ) # convert to line-array
	ini=( ${ini[*]//;*/} )      # remove comments with ;
	ini=( ${ini[*]/\	=/=} )  # remove tabs before =
	ini=( ${ini[*]/=\	/=} )   # remove tabs be =
	ini=( ${ini[*]/\ =\ /=} )   # remove anything with a space around =
	ini=( ${ini[*]/#\\[/\}$'\n'cfg.section.} ) # set section prefix
	ini=( ${ini[*]/%\\]/ \(} )    # convert text2function (1)
	ini=( ${ini[*]/=/=\( } )    # convert item to array
	ini=( ${ini[*]/%/ \)} )     # close array parenthesis
	ini=( ${ini[*]/%\\ \)/ \\} ) # the multiline trick
	ini=( ${ini[*]/%\( \)/\(\) \{} ) # convert text2function (2)
	ini=( ${ini[*]/%\} \)/\}} ) # remove extra parenthesis
	ini[0]="" # remove first element
	ini[${#ini[*]} + 1]='}'    # add the last brace
	eval "$(echo "${ini[*]}")" # eval the result
}

cfg_writer ()
{
    IFS=' '$'\n'
    fun="$(declare -F)"
    fun="${fun//declare -f/}"
    for f in $fun; do
        [ "${f#cfg.section}" == "${f}" ] && continue
        item="$(declare -f ${f})"
        item="${item##*\{}"
        item="${item%\}}"
        item="${item//=*;/}"
        vars="${item//=*/}"
        eval $f
        echo "[${f#cfg.section.}]"
        for var in $vars; do
            echo $var=\"${!var}\"
        done
    done
}

And then you can parse your ini files as following:

# parse the config file called 'myfile.ini', with the following
# contents::
#   [sec2]
#   var2='something'
cfg.parser 'myfile.ini'

# enable section called 'sec2' (in the file [sec2]) for reading
cfg.section.sec2

# read the content of the variable called 'var2' (in the file
# var2=XXX). If your var2 is an array, then you can use
# ${var[index]}
echo "$var2"

Unfortunately, the cfg.parser() function do no support embedded spaces
in section names… yet

tcptraceroute

tcptraceroute was another friend of the network administrator. Probably you known classical traceroute, which use the TTL field in IP header to determinate the hops in the route to a specific destination. In each hop the TTL value is decreasing (according to internet protocol), and when TTL is equal to cero, a ICMP is returned to sender IP. So, the classical traceroute technique, send a UDP packet with TTL field setted to 1, and get the IP address of the first hop from returned ICMP, and likewise for other hops.

Unfortunately, today many host are firewalled and ICMPs are blocking. The classical traceroute design fails, and we only obtain a list of useless β€œ*”. The tcptraceroute use TCP packets instead of UDP packets, and try to connect to usual port enabling the SYN flag. If port is closed, a RST flag is returned, and if port is open then return an ACK flag. So we don’t need ICMPs anymore.

ssh socket

When work using a β€œcheap” wireless network (yes, I still have kind neighbours), each new ssh connection takes a lot of time, because a new authentication is required from the peer, but what happens if I already connected? In theory no new re-authentication is necessary, you can use existing socket to send data over the same channel of the previous connection. To enable socket manager, put the following lines in your ~/.ssh/config file:

Host *
  ControlMaster auto
  ControlPath ~/.ssh/socket-%r@%h:%p

Some situations may freeze your ssh connection, for example when the network goes down before close connection and timeout is reached, in this case the socket will also be frozen, and new connections to the same destination are no possible. Only need to remove the socket file in ~/.ssh/ directory and kill the previous session.