DEV Community

loading...
Cover image for Bash++ Classes

Bash++ Classes

John Robertson
Full Stack DevOp for Linux, Windows.
・3 min read

Introduction

Bash is an interpreted language not often associated with object oriented programming (OOP), probably because it does not provide first class facilities for this purpose. However, any language which can dynamically allocate memory and supports pointers can be used for OOP; in the end OOP is really just a style of managing data which is potentially located on the heap.

Historical Context

Back in the 1990's I got a gig writing a warehouse inventory app for a 16 bit Psion handheld computer. My choice of languages for this platform was either Basic or C. By that time I had been coding in C for ten years, and C++ for five. Given the available options, I of course chose C.
My experience with C++ had already skewed my thought processes towards OOP, so I endeavored to carry that forwards in C. A few hours of fiddling produced something like this:

// Class declaration
typedef struct _Class {
   int ival1;
   char *dyn_str;
   /* ... And so on ... */
} Class

// Constructor
Class* Class_constructor(
   Class *self,
   int ival,
   const char *str
)
{
   self->ival1= ival;
   self->dyn_str= strdup(str);
   return self;
}

// Destructor
void* Class_destructor(Class *self)
{
   if(self->dyn_str)
      free(self->dyn_str);
   return self;
}

// Member function
void Class_printStuff(const Class *self)
{
   printf("%s %d\n", self->dyn_str, self->ival1);
}

// Program starts here
int main()
{
   Class obj;
   Class_constructor(&obj, 25, "You feel like you are");
   Class_printStuff(&obj);
   Class_destructor(&obj);
   return 0;
}

Hmmm, that looks pretty similar to C++, doesn't it? I added a few short macros to provide functionality of C++'s 'new' and 'delete' operators, and I was off to the races. The client was well pleased with the final product, which had a GUI that looked a whole lot like smart phone apps look today. With this success under my belt I continued to pursue OOP in C. Within a year I realized that was I writing OO code much faster in C than I could in C++. Furthermore, the compatibility of various C++ compilers at that time was deplorable.
I went on to work out polymorphism and virtual functions, as well as a standard toolkit with all the modern facilities required for secure multithreaded OO programming. To this day I am far more productive writing OO programs in C than C++, and do so whenever C, or a mix of C and C++ is a practical option.

Fast Forward

Roughly 5 years ago I started toying with the idea of OOP in Bash. After a few hours working on this I ran up against Bash's lack of a generalized function return facility (without invoking a subshell which wreaks havoc with data concurrency). Paying work and life intervened, and it is only this week the idea bubbled back up to the fore of my thoughts.

Extending Bash

Let me be clear: I have no intention of modifying the source code of Bash itself; Bash's main value proposition is being a robust, flexible, and mature interpreter which is already installed on just about every POSIX platform - read: embedded Linux & BSD. Throw in non-interactive use of ssh and you can now query and/or administrate tens of thousands of heterogeneous embedded systems in parallel.

Return Facility: done

Earlier this week I posted an implementation in Bash for a return stack. This not only solves the original problem, but supports quite well multiple return values from any function, a la Python.

OOP

Subsequently I have implemented 'malloc', as well as operators 'new', 'delete', 'fetch' (accessor), and 'call' (member functions).
This is how a working Bash class looks:

# import oop facilities
source ../bash++

function FirstClass::FirstClass ()
###################################
# Constructor for FirstClass
#
{
   local opts=$(shopt -o -p nounset)
   # Necessary to avoid "unbound variable" exception
   set +u

   local this=$1
   shift

   # Concatenate all constructor args separated by a space
   local catStr
   while [[ -n $1 ]]; do

      if [[ -z $catStr ]]; then
         catStr="$1"
      else
         catStr="$catStr $1"
      fi

      shift
   done

   # Assign value to class member
   eval $this[catstr]='$catStr'

   # Restore options
   eval $opts
}

function FirstClass::~FirstClass ()
###################################
# Destructor for FirstClass
#
{
   local this=$1

   # Free resources
   eval unset $this[catstr]
}

function FirstClass::wordCount ()
###################################
# Return the word count of catstr
# Arguments:
#   None
# Returns:
#   Word count on the return stack
#
{
   # For clarity
   local this=$1

   # Retrive the catstr member value
   fetch $this.catstr; RTN_pop R1

   # Run through 'wc' command, store result
   # on return stack.
   RTN_push $(wc -w <<<"$R1")

}

###################################
### Execution starts here #########
###################################

# Create an instance of FirstClass
new FirstClass 'Here are' '3 constructor' 'arguments'

# Pop the address of the object into a handle
RTN_pop h_fc

# Debug print object to stdout
show $h_fc

# Access a member value
fetch $h_fc.catstr; RTN_pop str

# Print member value
echo "catstr= '$str'"

# Get the word count
call $h_fc.wordCount; RTN_pop n
echo "word count= $n"

# Delete object
delete $h_fc

Conclusion

If you find this sort of thing interesting, please check out my bash++ project on Github!

Discussion (0)