Shell Script Wrapper Examples: Enhance the Ping and Host Commands

by on June 19, 2012 · 11 comments· LAST UPDATED June 19, 2012

in Linux, Shell scripting, UNIX

Shell script wrappers can make the *nix command more transparent to the user. The most common shell scripts are simple wrappers around third party or system binaries. A wrapper is nothing but a shell script that includes a system command or utility.

Linux and Unix like operating system can run both 32bit and 64bit specific versions of applications. You can write a wrapper script that can select and execute correct version on a 32bit or 64bit hardware platform. In cluster environment and High-Performance computing environment you may find 100s of wrapper scripts written in Perl, Shell, and Python to get cluster usage, setting up shared storage, submitting and managing jobs, backups, troubleshooting, invokes commands with specified arguments, sending stdout to stdout and stderr to stderr and much more.

In this post, I will explains how to create a shell wrapper to enhance the basic troubleshooting tool such as ping and host.

Why use shell script wrappers

  1. Time saving.
  2. Customize collection of *nix commands.
  3. Passing default arguments to binaries or 3rd party apps.
  4. Call to start the desired job.
  5. Wrappers are perfect when tools or command that require customized environmental variable settings, system controls or job-submission parameter.
  6. Useful in HPC, clustered environment, *nix sysadmin, and scientific research.

Example: Creating a shell script wrapper

The following shell script will start java app called kvminit by setting up system environment and redirect logs to a log file:

 
#!/bin/sh
# Wrapper for our kvm app called kvminit
export JAVA_HOME=${JAVA_HOME:-/usr/java}
export CLASSPATH="/home/vivek/apps/java/class
exec ${JAVA_HOME}/bin/java kvminit "$@" &>/var/log/kvm/logfile

Another wrapper script that can start / stop / nfs server or client in a single pass:

 
#!/bin/bash
# A shell script to start / stop / restart nfsv4 services 
_me=${0##*/}              #Who am I? Server or client?
_server="/etc/init.d/rpcbind /etc/init.d/rpcidmapd /etc/init.d/nfslock /etc/init.d/nfs" # list of server init scripts
_client="/etc/init.d/rpcbind /etc/init.d/rpcidmapd /etc/init.d/nfslock" # list of client init scripts
_action="$1"            # start / stop / restart
 
# run all scripts with one action such as stop or start or restart
runme(){
	local i="$1"
	local a="$2"
	for t in $i
	do
		$t $a
	done
}
 
usage(){
	echo "$_me start|stop|restart|reload|status";
	exit 0
}
 
[ $# -eq 0 ] && usage
 
# main logic - take action
case $_me in
	nfs.server) runme "$_server" "$_action" ;;
	nfs.client) runme "$_client" "$_action" ;;
	*) usage
esac
 

Tools for troubleshooting: ping and host commands

As a sysadmin, I use basic troubleshooting tools such as ping and host frequently.

  • The ping commands help determine connectivity between devices on my network or remote network.
  • The host command help determine DNS problems. I can get information about Domain Name System records for specific IP addresses and/or host names so that I can troubleshoot DNS problems.

My problem with ping and host command

Both commands will not work when you pass protocol names or username:password from bash history:
curl -I http://www.cyberciti.biz/
ping !!:2

Sample outputs:

ping http://www.cyberciti.biz/
ping: unknown host http://www.cyberciti.biz/

OR
curl -I http://www.cyberciti.biz/
host !!:2

Sample outputs:

host http://www.cyberciti.biz/
Host http://www.cyberciti.biz/ not found: 3(NXDOMAIN)

So I decided to write a bash shell function ping() and host() that can use any one of the following format of domain and pass it to ping and host commands:

http://www.cyberciti.biz/
http://www.cyberciti.biz/file/one.html
scp://www.cyberciti.biz/foo
ftp://backup.cyberciti.biz/path
https://user:password@cp.cyberciti.biz/

_getdomainnameonly()

 
# Name: _getdomainnameonly
# Arg: Url/domain/ip
# Returns: Only domain name
# Purpose: Get domain name and remove protocol part, username:password and other parts from url
_getdomainnameonly(){
        # get url 
	local h="$1"
        # upper to lowercase 
	local f="${h,,}"
	# remove protocol part of hostname
        f="${f#http://}"
        f="${f#https://}"
	f="${f#ftp://}"
	f="${f#scp://}"
	f="${f#scp://}"
	f="${f#sftp://}"
	# Remove username and/or username:password part of hostname
	f="${f#*:*@}"
	f="${f#*@}"
	# remove all /foo/xyz.html*  
	f=${f%%/*}
	# show domain name only
	echo "$f"
}
 

The $ character is used for parameter expansion, and command substitution. See how to use bash parameter substitution for more information.

ping() wrapper

 
# Name: ping() wrapper
# Arg: url/domain/ip
# Purpose: Send ping request to domain by removing urls, protocol, username:pass using system /bin/ping
ping(){
	local t="$1"
	local _ping="/bin/ping"
	local c=$(_getdomainnameonly "$t")
	[ "$t" != "$c" ] && echo "Sending ICMP ECHO_REQUEST to \"$c\"..."
	$_ping $c
}
 

Both ping() and bash() [see below] are bash functions to performs a specific task.

host() wrapper

 
# Name: host() wrapper
# Arg: Domain/Url/IP
# Purpose: Dns lookups system /usr/bin/host
host(){
	local t="$1"
	local _host="/usr/bin/host"
	local c=$(_getdomainnameonly "$t")
	[ "$t" != "$c" ] && echo "Performing DNS lookups for \"$c\"..."
	$_host $c
}
 

Create a shell script called $HOME/scripts/wrapper_functions.lib and put all above three functions as follows:

#!/bin/bash
## Note: Only works with bash 3.x or 4.x+ ##
_getdomainnameonly(){
	local h="$1"
	local f="${h,,}"
	# remove protocol part of hostname
        f="${f#http://}"
        f="${f#https://}"
	f="${f#ftp://}"
	f="${f#scp://}"
	f="${f#scp://}"
	f="${f#sftp://}"
	# remove username and/or username:password part of hostname
	f="${f#*:*@}"
	f="${f#*@}"
	# remove all /foo/xyz.html*  
	f=${f%%/*}
	# show domain name only
	echo "$f"
}
 
ping(){
	local t="$1"
	local _ping="/bin/ping"
	local c=$(_getdomainnameonly "$t")
	[ "$t" != "$c" ] && echo "Sending ICMP ECHO_REQUEST to \"$c\"..."
	$_ping $c
}
 
host(){
	local t="$1"
	local _host="/usr/bin/host"
	local c=$(_getdomainnameonly "$t")
	[ "$t" != "$c" ] && echo "Performing DNS lookups for \"$c\"..."
	$_host $c
}
 

Edit your $HOME/.bashrc and append the following line:

 
source $HOME/scripts/wrapper_functions.lib
 

Save and close the file. You can load wrappers instantly by typing the following command:
$ source $HOME/scripts/wrapper_functions.lib
Test it as follows:

 
# First, just see header 
curl -I http://cyberciti.biz/
 
## Now call it from history i.e. call 2nd arg passed to the curl ##
host !!:2
 
## Again call it from history i.e. call 1sat arg passed to the host ##
ping !!:1
 
## Other usage ##
host http://username:password@cyberciti.biz/
ping ftp://cyberciti.biz/foo
 

Sample outputs:

Bash wrappers example

Fig.01: Bash wrapper in action

How do I use real ping and host command

Simply use the following syntax:
/bin/ping -c4 cyberciti.biz
OR
/usr/bin/host cyberciti.biz ns1.example.com

How do I pass command line arguments via bash shell script wrapper?

Following modified bash code passes the command line arg to real system /bin/ping and /usr/bin/host:

#!/bin/bash
# Note: Works with bash 4.x and above only #
_getdomainnameonly(){
	local h="$1"
	local f="${h,,}"
	# remove protocol part of hostname
        f="${f#http://}"
        f="${f#https://}"
	f="${f#ftp://}"
	f="${f#scp://}"
	f="${f#scp://}"
	f="${f#sftp://}"
	# remove username and/or username:password part of hostname
	f="${f#*:*@}"
	f="${f#*@}"
	# remove all /foo/xyz.html*  
	f=${f%%/*}
	# show domain name only
	echo "$f"
}
 
 
ping(){
	local array=( $@ )  		# get all args in an array
	local len=${#array[@]}          # find the length of an array
	local host=${array[$len-1]}     # get the last arg
	local args=${array[@]:0:$len-1} # get all args before the last arg in $@ in an array 
	local _ping="/bin/ping"
	local c=$(_getdomainnameonly "$host")
	[ "$t" != "$c" ] && echo "Sending ICMP ECHO_REQUEST to \"$c\"..."
	# pass args and host
	$_ping $args $c
}
 
host(){
	local array=( $@ )
	local len=${#array[@]}
	local host=${array[$len-1]}
	local args=${array[@]:0:$len-1}
	local _host="/usr/bin/host"
	local c=$(_getdomainnameonly "$host")
	[ "$t" != "$c" ] && echo "Performing DNS lookups for \"$c\"..."
  	$_host $args $c
}
 

Run it as follows:

curl -I http://cyberciti.biz/
ping -v -c 2 !!:2
host -t aaaa !!:4
host -t mx !!:3
host -t a ftp://nixcraft.com/
host -a ftp://nixcraft.com/
ping 8.8.8.8
host 8.8.8.8

Sample outputs:

Bash wrapper with command line args

Fig.02: Bash wrapper with command line args in action


See how to find the last argument passed to a shell script and extract all command line arguments before last parameter in $@ for more information.

Conclusion

In this example, I wrote small short script and functions, that do a minimal operation, and then calling then in another script or directly to achieve my result. This is the basis of Unix and Linux. You need to reuse code and existing binaries to create desired functionality. Have a favorite wrapper script? Let's hear about it in the comments.

TwitterFacebookGoogle+PDF versionFound an error/typo on this page? Help us!

{ 11 comments… read them below or add one }

1 Sergio Luiz Araujo Silva June 19, 2012 at 3:56 pm

Exceptional work, learned a lot from this script and certainly increased my love of the shell a little more. Not to go unnoticed would like to send this page: http://orangesplotch.com/bash-going-up/

Reply

2 qswb June 20, 2012 at 9:21 am

Wow this is awesome!

but……. i get this error for any command i run [host,ping]

bash: ${h,,}: bad substitution

what am i doing wrong?
thanks!

Reply

3 nixCraft June 20, 2012 at 10:53 am

@qswb,

You are using old version of bash. This will only work in latest version of bash. Find:

local f="${h,,}"

Replace with:

local f="$(echo "$h" | tr '[A-Z]' '[a-z]')"

This should fix the problem for older version.

Reply

4 Iuri Gomes Diniz June 20, 2012 at 1:54 pm

Instead of using /bin/ping in order to get real ‘ping’, you can use the command shell bultiin:

command ping

Also, you can use command in order to get same behavior of:

local _ping="/bin/ping"
$_ping $args $c

The advantage is that command ping search for ping on $PATH, you don’t need to now where real ‘ping’ is.

Reply

5 qswb June 20, 2012 at 10:33 pm

GNU bash, version 3.2.39-release x86_64

WORKS!

Reply

6 Shantanu June 21, 2012 at 9:31 am

Hi,

Couple of ideas:
* I think, the “absh 4″ism should be explicitly mentioned, as this can throw people off! :)
* using ‘/etc/init.d/’ in the variable list is not necessary, it can be put in the command.

Regards,
Shantanu

Reply

7 LinuxRawkstar June 21, 2012 at 2:56 pm

Haven’t you ever heard of sed? Dang!

Reply

8 BasketCase June 21, 2012 at 4:12 pm

A couple of shortcuts…

Instead of !!:2 for the second parameter of the previous command you can use !$ for the last parameter of the previous command since the last one is usually what you want.

Instead of /bin/ping or ‘command ping’ to use bypass aliases with a simple escape as in ‘\ping’.

Reply

9 nixCraft June 21, 2012 at 4:28 pm

@Shantanu,
I did mentioned in script

## Note: Only works with bash 3.x or 4.x+ ##

@LinuxRawkstar,
Why use 3rd party utility? You can save time and cpu cycle with bash builtin.

@BasketCase,
True, but in this case I’m using ping() and not an alias so I can not simply by pass using \ping syntax.

Appreciate all comments.

Reply

10 LinuxRawkstar June 21, 2012 at 5:29 pm

sed is in no way a “3rd party” utility. It’s a core staple of Linux and Unix for decades. Unlike these version-dependent bash-isms, it uses actual readable regular expressions and is compatible pretty much everywhere.

Reply

11 Nathan June 21, 2012 at 8:18 pm

Genius!

Reply

Leave a Comment

Tagged as: , , , , , , , , , , , , , , , , , , ,

Previous post:

Next post: