Here’s a thought experiment - you can only program in bash, and you have been assigned a task that demanded you to model your program with behaviors that could be easily modeled with objects eg. an airplane or a car (you get the idea). Imagine the complexities that might arise from a command-styled, procedurally programmatic language like bash to define the modularity, components, and properties of a vehicle.
Well, driven by my curiosity and lots of free time, I sought to explore how we might go about implementing object-oriented programming in bash.
Object Oriented Programming Principles
Object-Oriented Programming (OOP) is a paradigm that uses “objects” to design applications. These objects bundle related data, known as state, and methods to manipulate this data, known as behavior. Objects interact via message passing which invokes methods, allowing dynamic behavior. OOP is implemented using techniques like dynamic dispatch and closures. Behaviors are defined in a class but executed at runtime. A key requirement for OOP is lexical scoping, which allows a function to access variables from its enclosing scope. Without it, OOP implementation becomes challenging.
Object Oriented Example
Below shows a class diagram that illustrates a PlantUML example of how we can apply the OOP Principles
In our UML Diagram, a base class Animal with subclasses Dog, Cat, and Mosquito inherits from Animal and overrides the sound method. The State class is associated with the Animal class and has two properties: isAlive and Health. This class represents the state of an animal, indicating whether it’s alive and its health status.
classDiagram
Animal <|-- Dog
Animal <|-- Cat
Animal <|-- Mosquito
Animal : +String name
Animal : +int age
Animal: +sound() String
State <|-- Animal
State: +boolean isAlive
State: +String Health
class Dog{
+String breed
+sound() String (returns "woof")
+findOwner()
}
class Cat{
+String color
+sound() String (returns "meow")
+scratch()
}
class Mosquito{
+sound() String (returns "eeeeeeeeë")
+feed()
}
Experimentation
Compgen is used as a command in Unix-like operating systems, to generate possible completions for commands and filenames. For example, running compgen -c will list all the available commands, while compgen -f will generate a list of filenames in the current directory.
We utilize a this variable to declare a pointer variable which we could then use to initialize a static member function, The constructor can be called with the 1$ which references itself and performs the memory allocation before it has been completely initialized for future classes or functions that have not yet been created. We can utilize the built-in export keyword in bash to create variables that can be accessed through the child processes created by the subsequent scripts. By utilizing compgen, we could enable bash files to inherit methods between files, allowing the program execution to access the built-in methods of the class. This allows for methods to dynamic bind explicitly to another class and allows for the method cast to be called.
Class structure setup by simulating the _this keyword.
Heres an example:
|
|
Project Setup
|
|
Getting Animal to inherit from State. Consider state.sh as a base class file to define a state object.
Based on our UML diagram, we need to create a base class called State. through the OOP concept of inheritence, we want the attributes of the State class to be inherited from the Animal class and the animals we create later in the application to all inherit from the state class. This means each Animal will have a boolean attribute isAlive and a String attribute Health indicating their current state.
state.sh
|
|
Testing inheritance in the Animal class and defining the attributes
We create an Animal class to test the inheritance of the state object, for testing purposes we utilize the animal class to create animal states for dog, cat and mosquito. As you can see, the state functions and variables can be invoked in the animal class. This proves that lexical scoping can be achieved by calling the state variables with an underscore eg. _isAlive or _animalState.
animal.sh
|
|
output from animal.sh (reformatted to show property and method data)
The data can be transiently accessed through the animal class, which means that theoretically, we can abstract the animal into other classes eg. dog.sh, cat.sh or mosquito.sh.
|
|
Creating Animal Objects - Implementation
File Structure
|
|
Rewriting the animal class into an abstract class.
Now by utilizing the same idea as State() we can simulate the same idea of creating the inheritnace of our animal attributes using the _this keyword and compgen. Abstract classes are designed to be a generalization of animals, and it is not meant to be instantiated on their own. Instead, it serves as a blueprint for more specific classes, often called concrete classes eg. Dog(), Cat(), Mosquito(), which inherit from the Animal class.
animal.sh
|
|
To demonstrate the simulation of inheriting from an abstract class, I will generalize the application layer into a main file called main.sh. The methods and attributes of our abstract class Animal() has been inherited and are accessible to our concrete class. This theoretically implies that we can also utilize OOP concepts such as polymorphism and Encapsulation to manipulate our programs through base Inheritance and Abstraction.
main.sh
|
|
output from main.sh (simulated as a general environment to create object classes)
The abstract class data of animal can be accessed transiently in our main function and used to initialize different animals.
./main.sh
dog (woof, 12, , true, healthy)
cat (meow, 9, , true, obese)
mosquito (eeeeeeë, 0.5, , false, splattered)
In Conclusion
“If you have to write bash functions, you might as well write it in Python or Golang”
Bash is painful to write unless you’re using it to mock up a procedural, command-styled setup or linker script - it’s not worth trying to model Minecraft using pure bash. While the ideas I’ve proposed are theoretically possible and can be simulated using bash utils - and the idea of writing fully-fledged OO scripts in CICD processes might be tempting, but the juice is simply not worth the squueze.