ATTENTION: You need to have solid concepts of OOP and Bash before reading it, I will not explain nothing of that here.
First at all create an script in which you will define your class, and give it execution permissions.
touch vector.sh chmod +x vector.shThen copy the following code inside the script:
#!/bin/bash # Base class. (1) function Vector() { # A pointer to this Class. (2) base=$FUNCNAME this=$1 # Inherited classes (optional). (3) export ${this}_inherits="Class1 Class2 Class3" # (3.1) for class in $(eval "echo \$${this}_inherits") do for property in $(compgen -A variable ${class}_) do export ${property/#$class\_/$this\_}="${property}" # (3.2) done for method in $(compgen -A function ${class}_) do export ${method/#$class\_/$this\_}="${method} ${this}" done done # Declare Properties. (4) export ${this}_x=$2 export ${this}_y=$3 export ${this}_z=$4 # Declare methods. (5) for method in $(compgen -A function) do export ${method/#$base\_/$this\_}="${method} ${this}" done } # Human readable representation of the vector. (6) function Vector_show() { # (7) base=$(expr "$FUNCNAME" : '\([a-zA-Z][a-zA-Z0-9]*\)') this=$1 x=$(eval "echo \$${this}_x") y=$(eval "echo \$${this}_y") z=$(eval "echo \$${this}_z") echo "$this ($x, $y, $z)" } # Adds two vectors. function Vector_add() { base=$(expr "$FUNCNAME" : '\([a-zA-Z][a-zA-Z0-9]*\)') this=$1 other=$2 # Get it's components x1=$(eval "echo \$${this}_x") y1=$(eval "echo \$${this}_y") z1=$(eval "echo \$${this}_z") x2=$(eval "echo \$${other}_x") y2=$(eval "echo \$${other}_y") z2=$(eval "echo \$${other}_z") # Add it's components x=$(($x1 + $x2)) y=$(($y1 + $y2)) z=$(($z1 + $z2)) # Create a new vector. (8) Vector 'vector3' $x $y $z $vector3_show }
Explanation
- This is function is an equivalent of the constructor of a class. It must have the same name of the class. The name of a class can't contains underscores (_). We will use this function to create instances of the "Vector" class.
- Here you can get a reference of the base class of the object and a reference of the object created. The first argument of the constructor must be always the name of the object to create.
-
This block of code simulates multiple inheritance, is optional and you can remove it if you don't need.
-
Every property and method of the class can be accessed as ${this}_methodProperty_name, or you can also call to it's base class as ${base}_methodProperty_name.
Multiple inheritance works overwriting the methods and properties (m&p) of the preceding classes as follows:
Class1 < Class2 < Class3 < Base
- Class2 overwrites m&p of Class1.
- Class3 overwrites m&p of Class1 and Class2.
- Base overwrites m&p of Class1 and Class2 and Class3.
- This will export the m&p of the base class as a m&p of the object. See bellow.
-
Every property and method of the class can be accessed as ${this}_methodProperty_name, or you can also call to it's base class as ${base}_methodProperty_name.
Multiple inheritance works overwriting the methods and properties (m&p) of the preceding classes as follows:
- Every method and property of the class is exported as a global variable in the form of objectName_methodProperty_name. You can initialize it's properties through the constructor's parameters, or with a default value, or leave it uninitialized.
- This will find every function with the pathern BaseClass_* and export it as a global variable replacing BaseClass with the object's name, the value of the variable will be a string with the base class method and the object name as first parameter that serves as a reference to it.
- The methods of the class are defined as BaseClass_methodName. Method names can contains underscores, but camelCase is recommended.
- Obtains a reference to the base class (not obligatory) and the object, remember, the first argument is the name of the object.
- Of course, you can create new objects inside the methods of the same class.
#!/bin/bash # Import the class definition file. . vector.sh function main() { # Create the vectors objects. (1) Vector 'vector1' 1 2 3 Vector 'vector2' 7 5 3 # Show it's properties. (2) echo "vector1 ($vector1_x, $vector1_y, $vector1_z)" echo "vector2 ($vector2_x, $vector2_y, $vector2_z)" # Call to it's methods. $vector1_show $vector2_show $vector1_add vector2 } main
Explanation
- Objects are declared as Class objectName [arg1 arg2 arg3 ...].
- Now you can access to it's methods and properties as $objectName_methodsProperty_name [arg1 arg2 arg3 ...].
$ ./main.sh vector1 (1, 2, 3) vector2 (7, 5, 3) vector1 (1, 2, 3) vector2 (7, 5, 3) vector3 (8, 7, 6)OOP in Bash unlocked. Achievement reached :D
Me fascino!! :D
ReplyDeleteGracias :)
ReplyDeleteyour font makes this site unreadable.
ReplyDeleteHi, thanks for your advice, I have changed the font from Impact 12px to Verdana 16px. that seems to fix the problem :)
DeleteHi,
ReplyDeleteThanks a lot for explaining this topic!
I have some trouble trying to understand how these lines work:
export ${property/#$class\_/$this\_}="${property}" # Why not '${property} ${this}' as in the line below?
export ${method/#$class\_/$this\_}="${method} ${this}"
$(eval "echo \$${this}_inherits") # Curious about '\$$' syntax
It'll be great if someone will kindly explain or point me to some relevant readings. :)
I have some trouble trying to understand how these lines work:
Deleteexport ${property/#$class\_/$this\_}="${property}" # Why not '${property} ${this}' as in the line below?
export ${method/#$class\_/$this\_}="${method} ${this}"
Because a method needs to know who is calling for applying it's operations to the right object, while a property already contains a reference to it's owner object.
$(eval "echo \$${this}_inherits") # Curious about '\$$' syntax
The first \$ is just the literal character $ inside the string, the second $ is part of ${this} that will be replaced for the contents of the this variable.
In the example, you must read the expression:
eval "echo \$${this}_inherits"
as follows:
1) Replace ${this} by Vector:
eval "echo \$Vector_inherits"
2) Since $Vector_inherits is a dynamic variable and I need to read it's contents, I eval the expression inside the quotes: eval "...", that is echo $Vector_inherits.
Thanks for the explanation.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteCan you please explain , why are we declaring the variable base as ==> base=$(expr "$FUNCNAME" : '\([a-zA-Z][a-zA-Z0-9]*\)') at line number 42 and 55 , as we are not using this variable inside those functions in vector.sh??
ReplyDeleteAlso could you provide some more examples where we are actually making use of the inherited classes ==> export ${this}_inherits="Class1 Class2 Class3" # (3.1)
I am very interested to learn more on OOPS in bash script. Can you help me in knowing some good book/site/material ??
$base is declared in case you want to access to some method or property from the base class, I didn't used because it was not required for the example.
DeleteAnyways, you are right, the example was too simple, I'll try to make it more complete in the future, right now I'm very busy.
About books talking about this topic, mmh..., thats really hard to find. OOP isn't natively supported by Bash, so what presented here was a pure hackish way to do it. As @Daniel Lemke, linked here, there are many ways to mimic OOP in Bash, just search for it ;).
Oh god, this looks more like structural programming thinly disguised as OOP. This is a better example:
ReplyDeletehttp://lab.madscience.nl/oo.sh.txt
Yeah, I know, I'm not Bash master at all :P, indeed tanks for example, I didn't know about the :: operand.
ReplyDeleteI like the use of the Class_func call inside a variable name passing the object instance name as the first parameter.
ReplyDeleteI found that I often need to loop over a list of objects. In this case, my variable contains the object name
so I need an extra level of de-referencing with an extra eval.
I want to be able to do this:
for PtrPbj in AOBJ BOBJ COBJ ; do
eval \$${PtrObj}_initialize
done
without the extra eval and \$
eg. If my object is named AOBJ and I have a reference to it like this PtrAOBJ="AOBJ" then when I want to call
AOBJ_func using PtrAOBJ, I need to do this:
val \$${PtrAOBJ}_func "param_to_AOBJ_func"
If instead of creating variables like
export AOBJ_func="Class_func AOBJ"
I create a function
AOBJ_func() { Class_func AOBJ $@; }
then I can do one less level of eval by calling it like this:
${PtrAOBJ}_func "param_to_func" # works because it expands to AOBJ_func which can be evaluated directly without another eval.
Previously I was trying to re-parse the whole class function for every instance, but
now with this simple 1 line function, I can get the same result.
GÓWNO, a nie programowanie obiektowe ...
ReplyDeleteThis is not what bash is for. It's called python look it up...
ReplyDeletebash is smaller, faster, and language agnostic
DeleteNO modules needed
Thanks a lot! :$
ReplyDeleteI try to publish articles that are on my interest, and that's normally hard to find in other websites/blogs, or not very well explained. I'm not a regular writer, but i try every article to count.
Programming is combination of intelligent and creative work. Programmers can do anything with code. The entire Programming tutorials that you mention here on this blog are awesome. Beginners Heap also provides latest tutorials of Programming from beginning to advance level.
ReplyDeleteBe with us to learn programming in new and creative way.
hi i like to dive into this stuff, as i am a bit familiar with bash. at your declare methods part however, i think i found a bug.
ReplyDelete# Declare methods. (5)
for method in $(compgen -A function)
do
export ${method/#$base\_/$this\_}="${method} ${this}"
done
it will export all found functions (by compgen) and adds the current vector to it, you see it when you run env after you've run this script. i reckon you need a check if the $method contaisn the $base first, before exporting it
e.g. if [[ $method == $base_* ]]; then export.....
Yes, there is a bug, but it's at line 32:
DeleteChange: for method in $(compgen -A function)
To: for method in $(compgen -A function ${base}_)
Nice... staying basic bash-y, but thinking and structuring oo for state and behavior. The challenge though is still the creation of gazillions of globals... as long as bash does not get an extension for structured variables, there is nothing that can prevent that... Writing a 'cap' is a great stop gap solution... did it myself several times, for PL/I and even COBOL, including VM with garbage collection (back in end 80' / early 90').
ReplyDeleteI think I'd finally snap if I ever saw a coworker attempt to do something like this in a bash script...
ReplyDeleteHi,
ReplyDeleteThis piece of code is great. I'm using it!
How can I put it in a function? Here is the context, instead of doing $vector1_show, I want to put the object name in a string variable:
function myfct (){
vec=$1
$\$vec_show
}
myfct "vector1"
here $vec contains the name of the object
In this situation it is stupid to use it via a function, but in my situation (more complicated) I have to do that...
just easy python, ruby node or whatever X(
ReplyDeletenice
ReplyDeletei have grepper installed, so at the bottom of the code it shows div class="open_grepper_editor" title="Edit & Save To Grepper"/div but with tags XD XD :3
ReplyDeleteI was inspired by this blog and I used it to write oo_jq (see https://www.virtualtwigs.com/articles/oo_jq_article)
ReplyDelete