Creating classes and objects using bash scripting
Asked Answered
A

7

48

I'm trying to use bash scripting to make an script act like a phone book, so i tried to create classes and objects but unfortunately i couldn't find a way to do that ! so i'm asking how to create a class using bash scripting??

Ahrens answered 21/4, 2016 at 13:27 Comment(1)
You can't; bash is not an object-oriented language.Oklahoma
C
23

Bash is a scripting language, that doesn't support OOP, so you can't. Try Python.

The only other thing you could do is have several arrays, but that's messy. Use the index to link them.

Calk answered 21/4, 2016 at 13:30 Comment(3)
Detailed example here: hipersayanx.blogspot.com/2012/12/…Benally
@Benally that is a lot of code for a rather static "class". I think Maxim's answer on here is closer to a class.Calk
True, though I do see he included a multiple-inheritance (?!) capability so if you take that out, it trims the code down a bit. Thinking about it, why stop there, at OO, how about a "functional" bash, say Idris-like? In all seriousness, this gets back to your original point about Python, and thinking a line from Jurassic Park, even though you can have OO bash (or fp-bash), doesn't mean you should do it - that's what Python is for.Benally
T
103

You can try to do something like this

example.sh

#!/bin/bash

# include class header
. obj.h
. system.h

# create class object
obj myobject

# use object method
myobject.sayHello

# use object property
myobject.fileName = "file1"

system.stdout.printString "value is"
system.stdout.printValue myobject.fileName

obj.h

obj(){
    . <(sed "s/obj/$1/g" obj.class)
}

obj.class

# Class named "obj" for bash Object

# property
obj_properties=()

# properties IDs
fileName=0
fileSize=1

obj.sayHello(){
    echo Hello
}

obj.property(){
    if [ "$2" == "=" ]
    then
        obj_properties[$1]=$3
    else
        echo ${obj_properties[$1]}
    fi
}

obj.fileName(){
    if [ "$1" == "=" ]
    then
        obj.property fileName = $2
    else
        obj.property fileName
    fi
}

system.h

. system.class

system.class

system.stdout.printValue(){
    echo $($@)
}

system.stdout.printString(){
    echo $@
}

Link for reference: https://github.com/mnorin/bash-scripts/tree/master/objects The point is you can't create objects but you can emulate object-oriented programming in bash.

UPD: After all these years I actually decided to add a hint for "inheritance", because there is obviously no way to implement an actual inheritance, ALTHOUGH, combination may be visually presented like one. And to go this way we already have pretty much everything in the first example. With all the scripts above we can do a very simple thing like this:

obj2.h

obj2(){
    . <(sed "s/obj2/$1/g" obj2.class)
}

obj2.class

# Class named "obj2" for bash Object

obj2.sayGoodbye(){
    echo Goodbye
}

And the updated version of example.sh

#!/bin/bash

# include class headers
. obj.h
. obj2.h
. system.h

# create class object
obj myobject
obj2 myobject

# use object methods
myobject.sayHello
myobject.sayGoodbye # method "inherited" from obj2

myobject.fileName = "file1"

system.stdout.printString "value is"
system.stdout.printValue myobject.fileName

So, if you get lines obj myobject and obj2 myobject to look like inherit myobject from obj obj2, which is very simple, it will look a bit more like an inheritance (of course, it still won't be one).

UPD2: Following up on comments regarding making it work on bash3. Should be easy fix. Just replace $1 in obj.property with ${!1} and try to run it.

Tell answered 5/12, 2016 at 18:52 Comment(14)
An actual answer to the question even though it technically doesn't have one, plus 10 for creativity. One must understand OOP is just a concept. If you break it down to binary, nothing is inherently "OOP" anyway. So why not have objects in Shell simply like above.Disuse
Any hack for inheritance?Hon
I have an idea for an inheritance, will do it later probablyTell
waiting for the inheritance implementationEpiscopacy
I like the creativity, but with no way to define member functions and class properties on the fly, and no inheritance, it's not really a class. It's a group of functions that have to be defined separately that will have similar names.Calk
Exactly the point of the last sentence in the answer. You can't create any real classes or objects in bash.Tell
if you choose rather to create obj_properties (or any other associative array) using declare -Ag obj_properties, you will create a working associative array rather than relying on numerical indices that must be declared. This is beneficial in that you no longer need to declare a fixed number of indices but can actually dynamically generate elements on the fly.Odelet
If you use source class.h instead of . class.h wouldn't be better? There are any implications of using it?Beaty
Period is POSIX when "source" is not, so, it's better to use period instead of "source"Tell
Your answer inspired me... and I think I took it a little too fat..... see my answerPredominance
There is also an alternative answer using associative arrays: linkXmas
@Sunfloro, when using bash script, it doesn't matter if you use . or source, since it is not going to run in POSIX shell.. When using bash script, take "full" advantage of bash syntax without worrying about POSIX shell compatibility, but DO try to avoid stuff like mixing other scripting languages such as, sed, awk, grep in your bash script (which can be avoided using patterns). Similarly, when using POSIX shell script, take full advantage of that. This is what I have been taught and I use in practice where cross-platform portability is a concern.Anosmia
@minusone, great. btw this script is also using associative array (obj_properties[$1]=$3), so it is also not compatible with bash3.Anosmia
That was a brilliant read. Thank you.Institute
P
24

So I remember checking this question and answer a few years back .. and was thinking.... WHAT!?!?!

Then last week I took a closer look at @Maxims answer and then it became clear..

I have spent the last week and created a bash class transpiler and class loader for class object, with methods and other goodies.. all cause I wanted to create a terminal animation infrastructure:

two items being animated in the terminal

So while this is just a start I found this to be a SUPER cool and challenging adventure.. I hope my code would help someone else as well!!

BTW: Was tested only on MAC OS so some tweaking might be needed :)

Predominance answered 5/10, 2019 at 20:37 Comment(1)
Also added inheritance... :)Predominance
C
23

Bash is a scripting language, that doesn't support OOP, so you can't. Try Python.

The only other thing you could do is have several arrays, but that's messy. Use the index to link them.

Calk answered 21/4, 2016 at 13:30 Comment(3)
Detailed example here: hipersayanx.blogspot.com/2012/12/…Benally
@Benally that is a lot of code for a rather static "class". I think Maxim's answer on here is closer to a class.Calk
True, though I do see he included a multiple-inheritance (?!) capability so if you take that out, it trims the code down a bit. Thinking about it, why stop there, at OO, how about a "functional" bash, say Idris-like? In all seriousness, this gets back to your original point about Python, and thinking a line from Jurassic Park, even though you can have OO bash (or fp-bash), doesn't mean you should do it - that's what Python is for.Benally
P
2

Try with BashX: https://github.com/reduardo7/bashx (This is my project, I use it on several other projects)

Example

#!/usr/bin/env bash

# ...

@Actions.action1() { # \\n Action without arguments
  set -x
  pwd
  @log "
  Action 1
  Multi-Line
"
  ls -la
  bash
}

@Actions.action2() { # param1 [param2] \\n Action with arguments\\n\\tdescription second line\\nother line
  eval "$(@user.options 'new:-n|-N' 'path:-p|--path:true')"
  set -x

  @log "'n' or 'N' parameter: ${user_options_new}"
  @log "'p' or 'path' parameter: ${user_options_path[@]} (${#user_options_path[@]})"

  local param1="$1"
  local param2="$2"
  [[ "$param1" != 'asd' ]] && @throw.invalidParam param1

  @log Action 2
  @log Param1: $1
  @log Param2: $2
}

@app.run "$@"

Usage

./myscript action1
./myscript action2 -n -p /tmp 'my param 1'
Prairie answered 12/3, 2020 at 18:49 Comment(1)
It's good practice to denote that you are the author of a prescribed solution.Marillin
S
2

While there's no true way to create classes in Bash, you can get a little creative. I've found over the years that my preferred way to do this is to create a function that returns a command that can be executed to change state or read properties of the instance. The instance data can be stored in an array.

For example, if you wanted to make a class for binary search trees, you could have this in BinarySearchTree.sh:

__BINARYSEARCHTREE_INSTANCE_DATA__=()

__BINARYSEARCHTREE_INSTANCE_DATA_LENGTH__=()
__BINARYSEARCHTREE_INSTANCE_DATA_SIZE__=()

BinarySearchTree()
{
    echo "__BinarySearchTree__ $RANDOM "
}

__BinarySearchTree__()
{
    local id="$1"
    local code="$2"

    case "$code" in
        '.insert' | '[insert]' | "['insert']" | '["insert"]')
            local value="$3"

            if [ "${__BINARYSEARCHTREE_INSTANCE_DATA__["$id", 0] + set}" ]; then
                local length="${__BINARYSEARCHTREE_INSTANCE_DATA_LENGTH__["$id"]}"
                local new_node="$length"

                __BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$length"]="$value"

                length=$((length + 1))
                __BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$length"]=''

                length=$((length + 1))
                __BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$length"]=''

                local current_node=0
                local parent

                while [ 1 ]; do
                    parent="$current_node"

                    if [ "$value" -lt "${__BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$((current_node))"]}" ]; then
                        current_node="${__BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$((current_node + 1))"]}"

                        if [ "$current_node" == '' ]; then
                            __BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$((parent + 1))"]="$new_node"

                            break
                        fi
                    else
                        current_node="${__BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$((current_node + 2))"]}"

                        if [ "$current_node" == '' ]; then
                            __BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$((parent + 2))"]="$new_node"

                            break
                        fi
                    fi
                done

                __BINARYSEARCHTREE_INSTANCE_DATA_LENGTH__["$id"]="$((length + 1))"
                __BINARYSEARCHTREE_INSTANCE_DATA_SIZE__["$id"]="$((${__BINARYSEARCHTREE_INSTANCE_DATA_SIZE__["$id"]} + 1))"
            else
                __BINARYSEARCHTREE_INSTANCE_DATA__["$id", 0]="$value"
                __BINARYSEARCHTREE_INSTANCE_DATA__["$id", 1]=''
                __BINARYSEARCHTREE_INSTANCE_DATA__["$id", 2]=''
                __BINARYSEARCHTREE_INSTANCE_DATA_LENGTH__["$id"]=3
                __BINARYSEARCHTREE_INSTANCE_DATA_SIZE__["$id"]=1
            fi;;

        '.has' | '[has]' | "['has']" | '["has"]')
            local value="$3"

            local current_node=0

            if [ "${__BINARYSEARCHTREE_INSTANCE_DATA__["$id", 0] + set}" ]; then
                while [ 1 ]; do
                    local current_value="${__BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$((current_node))"]}"

                    if [ "$current_value" == "$value" ]; then
                        return 0
                    fi

                    if [ "$value" -lt "$current_value" ]; then
                        current_node=${__BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$((current_node + 1))"]}
                    else
                        current_node=${__BINARYSEARCHTREE_INSTANCE_DATA__["$id", "$((current_node + 2))"]}
                    fi

                    if [ "$current_node" == '' ]; then
                        return 1
                    fi
                done
            else
                return 1
            fi;;

        '.size' | '[size]' | "['size']" | '["size"]')
            if [ "${__BINARYSEARCHTREE_INSTANCE_DATA__["$id", 0] + set}" ]; then
                echo "${__BINARYSEARCHTREE_INSTANCE_DATA_SIZE__["$id"]}"
            else
                echo 0
            fi;;

        '.empty' | '[empty]' | "['empty']" | '["empty"]')
            if [ "${__BINARYSEARCHTREE_INSTANCE_DATA__["$id", 0] + set}" ]; then
                return 1
            else
                return 0
            fi;;

        '.clear' | '[clear]' | "['clear']" | '["clear"]')
            unset "__BINARYSEARCHTREE_INSTANCE_DATA__[$id, 0]"
    esac
}

And then make objects like this:

source './BinarySearchTree.sh'

process()
{
    local tree="$1"

    $tree.insert 52
    $tree.insert -150
    $tree.insert 42

    if $tree.has 42; then
        echo 'Has 42!'
    else
        echo 'Does not have 42!'
    fi

    $tree.clear

    echo "Size: $($tree.size)"
}

main()
{
    local tree=$(BinarySearchTree)

    process "$tree"
}

main "$#" "$@"

The advantages of this method are that objects can be passed into other functions and there are no external file operations. Even though this may seem impractical, it actually makes Bash quite a nice language to work in since you can modularize your classes.

Singletree answered 29/5, 2020 at 19:12 Comment(0)
H
0

I found a way that you can actually use object in bash, but you need think out of box. If we treat object as files, we can support OOP in bash. You can define a model or even validation for that, and store your object in a file like this:

# /tmp/obj1
var1=val1
var2=val2

# /tmp/obj2
var1=val11
var2=val22

and you can directly read object values from these files and do whatever you want with them. The point is we dont use bash variable here, its just text processing.
Hope you or other bash lovers can work with this method!

Heterogamete answered 6/2, 2023 at 7:10 Comment(0)
W
0

If you do not care about the appearance, you can achieve object orientation in the following ways.

oop.sh ( Core script )

function Send()
{
        local receiver=${2}
        declare -n members=${receiver}
        local selector=${1}

        shift 2

        ${members[$selector]} ${receiver} ${*}
}

This is an example of using oop.sh to switch scp transfer direction.

example.sh

#!/usr/bin/bash

source oop.sh

# Push class member definition
function Push_Copy()
{
    declare -n this=$1
    shift

    scp ${1} ${2}
}

# Push class definition
function Push()
{
    declare -n this=${1}
    this["Copy"]=Push_Copy
}

# Pull class member definition
function Pull_Copy()
{
    declare -n this=$1
    shift

    scp ${2} ${1}
}

# Pull class definition
function Pull()
{
    declare -n this=${1}
    this["Copy"]=Pull_Copy
}

# Assets class member definition
function Assets_CopyBy()
{
    declare -n this=${1}
    local operation=${2}

    # operation.Copy( "./file1.txt", "[email protected]:/home/user/file1.txt" )
    Send Copy ${operation} "./file1.txt" "[email protected]:/home/user/file1.txt"
    # operation.Copy( "./file2.txt", "[email protected]:/home/user/file2.txt" )
    Send Copy ${operation} "./file2.txt" "[email protected]:/home/user/file2.txt"
}

# Assets class definition
function Assets()
{
    declare -n this=${1}
    this["CopyBy"]=Assets_CopyBy
}

declare -A global_operation
declare -A global_assets

# global_assets = Assets()
Assets global_assets 

# global_operation = Pull()
Pull global_operation

# scp ./file1.txt [email protected]:/home/user/file1.txt
# scp ./file2.txt [email protected]:/home/user/file2.txt
# global_assets.CopyBy( global_operation )
Send CopyBy global_assets global_operation

# global_operation = Push()
Push global_operation 

# scp [email protected]:/home/user/file1.txt ./file1.txt
# scp [email protected]:/home/user/file2.txt ./file2.txt
# global_assets.CopyBy( global_operation )
Send CopyBy global_assets global_operation 

Succession required? If so, you can inherit in the following ways.

function Base_Name()
{
        declare -n this=${1}
        echo "Base"
}

function Base_Amount()
{
        declare -n this=${1}
        echo "0"
}

function Base()
{
        declare -n this=${1}
        this["Name"]=Base_Name
        this["Amount"]=Base_Amount
}

function Derived_Name()
{
        declare -n this=${1}
        echo "Derived"
}

function Derived()
{
        declare -n this=${1}
        Base ${1} # Derived inherits from Base
        this["Name"]=Derived_Name
}

declare -A global_object

Derived global_object
Send Name global_object   # -> Derived
Send Amount global_object # -> 0

Base global_object
Send Name global_object   # -> Base
Send Amount global_object # -> 0
Weedy answered 25/6 at 15:26 Comment(2)
Some explanation of the "why" would be helpful - you made the "how" pretty clear.Hindoo
Is that confirmation of why this method should be chosen? If so, the reason lies in the fact that diversity can be achieved with minimal implementation.Weedy

© 2022 - 2024 — McMap. All rights reserved.