43

Preface: I love bash and have no intention of starting any sort of argument or holy-war, and hopefully this is not an extremely naive question.

This question is somewhat related to this post on superuser, but I don't think the OP really knew what he was asking for. I use bash on FreeBSD, linux, OS X, and cygwin on Windows. I've also had extensive experience recently with PowerShell on Windows.

Is there a shell for *nix, already available or in the works, that is compatible with bash but adds a layer of object-oriented scripting into the mix? The only thing I know of that comes close is the python console, but as far as I can tell it doesn't provide access to the standard shell environment. For example, I can't just cd ~ and ls, then chmod +x file inside the python console. I would have to use python to perform those tasks rather than the standard unix binaries, or call the binaries using python code.

Does such a shell exist?

11
  • 3
    There's Pash but that's much more Powershell-like than Bash-like.
    – ephemient
    Dec 2, 2010 at 21:59
  • 1
    @ephemient maybe you should write an answer for pash... though I know nothing 'bout it, iirc, powershell is an OO shell. Dec 4, 2010 at 10:10
  • 4
    Hey, you should check out ipython. If you enter an expression that doesn't make sense as python, it will try to map it to a shell command. For example, stuff like cd ~ followed by ls works as in Bash. You can also assign output to Python variables (lists of lines . . sort of) with commands like listing = !ls.
    – intuited
    Dec 5, 2010 at 3:42
  • @intuited: awesome, I'll check it out Dec 5, 2010 at 3:45
  • 1
    @intuited: iPython has been pretty good for the stuff I want to do, thanks! Jan 7, 2011 at 19:23

15 Answers 15

46

I can think of three desirable features in a shell:

  • Interactive usability: common commands should be quick to type; completion; ...
  • Programming: data structures; concurrency (jobs, pipe, ...); ...
  • System access: working with files, processes, windows, databases, system configuration, ...

Unix shells tend to concentrate on the interactive aspect and subcontract most of the system access and some of the programming to external tools, such as:

  • bc for simple math
  • openssl for cryptography
  • sed, awk and others for text processing
  • nc for basic TCP/IP networking
  • ftp for FTP
  • mail, Mail, mailx, etc. for basic e-mail
  • cron for scheduled tasks
  • wmctrl for basic X window manipulation
  • dcop for KDE ≤3.x libraries
  • dbus tools (dbus-* or qdbus) for various system information and configuration tasks (including modern desktop environments such as KDE ≥4)

Many, many things can be done by invoking a command with the right arguments or piped input. This is a very powerful approach — better have one tool per task that does it well, than a single program that does everything but badly — but it does have its limitations.

A major limitation of unix shells, and I suspect this is what you're after with your “object-oriented scripting” requirement, is that they are not good at retaining information from one command to the next, or combining commands in ways fancier than a pipeline. In particular, inter-program communication is text-based, so applications can only be combined if they serialize their data in a compatible way. This is both a blessing and a curse: the everything-is-text approach makes it easy to accomplish simple tasks quickly, but raises the barrier for more complex tasks.

Interactive usability also runs rather against program maintainability. Interactive programs should be short, require little quoting, not bother you with variable declarations or typing, etc. Maintainable programs should be readable (so not have many abbreviations), should be readable (so you don't have to wonder whether a bare word is a string, a function name, a variable name, etc.), should have consistency checks such as variable declarations and typing, etc.

In summary, a shell is a difficult compromise to reach. Ok, this ends the rant section, on to the examples.


  • The Perl Shell (psh) “combines the interactive nature of a Unix shell with the power of Perl”. Simple commands (even pipelines) can be entered in shell syntax; everything else is Perl. The project hasn't been in development for a long time. It's usable, but hasn't reached the point where I'd consider using it over pure Perl (for scripting) or pure shell (interactively or for scripting).

  • IPython is an improved interactive Python console, particularly targetted at numerical and parallel computing. This is a relatively young project.

  • irb (interactive ruby) is the Ruby equivalent of the Python console.

  • scsh is a scheme implementation (i.e. a decent programming language) with the kind of system bindings traditionally found in unix shells (strings, processes, files). It doesn't aim to be usable as an interactive shell however.

  • zsh is an improved interactive shell. Its strong point is interactivity (command line edition, completion, common tasks accomplished with terse but cryptic syntax). Its programming features aren't that great (on par with ksh), but it comes with a number of libraries for terminal control, regexps, networking, etc.

  • fish is a clean start at a unix-style shell. It doesn't have better programming or system access features. Because it breaks compatibility with sh, it has more room to evolve better features, but that hasn't happened.


Addendum: another part of the unix toolbox is treating many things as files:

  • Most hardware devices are accessible as files.
  • Under Linux, /sys provides more hardware and system control.
  • On many unix variants, process control can be done through the /proc filesystem.
  • FUSE makes it easy to write new filesystems. There are already existing filesystems for converting file formats on the fly, accessing files over various network protocols, looking inside archives, etc.

Maybe the future of unix shells is not better system access through commands (and better control structures to combine commands) but better system access through filesystems (which combine somewhat differently — I don't think we've worked out what the key idioms (like the shell pipe) are yet).

14
  • 1
    You hit the nail on the head right after you say "I suspect this is what you're after". The main reason I'm asking this question is that I love having the power of unix tools, but the text-based interaction between programs definitely 'raises a barrier for more complex tasks'. I've spent enough of my programming days writing text parsers :) I think this is a very well thought out answer. It gets to the heart of the issue and the complexity of the subject. I wish I could upvote it twice :P Dec 2, 2010 at 23:03
  • 1
    +1 for ipython, though I have no idea what the OP wants to do.
    – Falmarri
    Dec 2, 2010 at 23:19
  • 1
    This is a great answer: I honestly think that the seeds of an interesting PhD thesis are here.
    – Ziggy
    Jul 31, 2012 at 5:29
  • 1
    @RobertSCiaccio This answer was just linked in a recent post, and your comment about text parses got me thinking... if text parsing is enough to accomplish your "complex tasks," then couldn't you have a small script or program that implements it and use it as some kind of function in your bash scripts? Just a thought, I don't have much bash scripting experience under my belt to speak of.
    – Oxwivi
    Dec 27, 2014 at 18:00
  • 1
    @onlyanegg In what way could fish be said to be “object-oriented”? Fish primarily aims at being simpler. Is there any way in which it's more powerful than the alternatives? Aug 13, 2015 at 7:07
14

You don't need much bash code to implement classes or objects in bash.

Say, 100 lines.

Bash has associative arrays that can be used to implement a simple Object system with inheritance, methods and properties.

So, you would might define a class like this:

class Queue N=10 add=q_add remove=q_remove

Creating an instance of this Queue might be done like this:

class Q:Queue N=100

or

inst Q:Queue N=100

Since classes are implemented with an array, class and inst are really synonyms - sort of like in javascript.

Adding items into this queue could be done like this:

$Q add 1 2 aaa bbb "a string"

Removing items into a variable X might be done like this:

$Q remove X

And dumping structure of an object could be done like this:

$Q dump

Which would return something like this:

Q {
      parent=Queue {
                     parent=ROOT {
                                   this=ROOT
                                   0=dispatch ROOT
                                 }
                     class=Queue
                     N=10
                     add=q_add
                     remove=q_remove
                     0=dispatch Queue
                   }
      class=Q
      N=4
      add=q_add
      remove=q_remove
      0=dispatch Q
      1=
      2=ccc ddd
      3=
      4=
    }

Classes are created using a class function like this:

class(){
    local _name="$1:"                            # append a : to handle case of class with no parent
    printf "$FUNCNAME: %s\n" $_name
    local _this _parent _p _key _val _members
    _this=${_name%%:*}                           # get class name
    _parent=${_name#*:}                          # get parent class name
    _parent=${_parent/:/}                        # remove handy :
    declare -g -A $_this                         # make class storage
    [[ -n $_parent ]] && {                       # copy parent class members into this class
        eval _members=\"\${!$_parent[*]}\"       # get indices of members
        for _key in $_members; do                # inherit members from parent
            eval _val=\"\${$_parent[$_key]}\"    # get parent value
            eval $_this[$_key]=\"$_val\"         # set this member
        done
    }
    shift 1

    # overwrite with specific values for this object
    ROOT_set $_this "$@" "0=dispatch $_this" "parent=${_parent:-ROOT}" "class=$_this"
}

NOTE: When defining a new class or instance, you can override any member value or function.

Bash associative arrays have a quirk that makes this work neatly: $Q[0]} is identical to $Q. This means that we can use array name to call a method dispatch function:

dispatch(){
    local _this=$1 _method=$2 _fn
    shift 2
    _fn="$_this[$_method]"                       # reference to method name
    ${!_fn} $_this "$@"
}

A down side is that I can not use [0] for data so my queues (in this case) start from index=1. Alternatively I could have used associative indices like "q+0".

To get and set members you might do something like this:

# basic set and get for key-value members
ROOT_set(){                                       # $QOBJ set key=value
    local _this=$1 _exp _key _val
    shift
    for _exp in "$@"; do
        _key=${_exp%%=*}
        _val="${_exp#*=}"
        eval $_this[$_key]=\"$_val\"
    done
}

ROOT_get(){                                       # $QOBJ get var=key
    local _this=$1 _exp _var _key
    shift
    for _exp in "$@"; do
        _var=${_exp%%=*}
        _key=${_exp#*=}
        eval $_var=\"\${$_this[$_key]}\"
    done
}

And to dump an object structure, I made this:

NOTE: This is not required for OOP in bash, but it is nice to see how objects are made.

# dump any object
obj_dump(){                                      # obj_dump <object/class name>
    local _this=$1 _j _val _key; local -i _tab=${2:-(${#_this}+2)}  # add 2 for " {"
    _tab+=2                                      # hanging indent from {
    printf "%s {\n" $_this
    eval "_key=\"\${!$_this[*]}\""
    for _j in $_key; do                          # print all members
        eval "_val=\"\${$_this[\$_j]}\""
        case $_j in
            # special treatment for parent
            parent) printf "%*s%s=" $_tab "" $_j; ${!_val} dump $(( _tab+${#_j}+${#_val}+2 ));;
                 *) printf "%*s%s=%s\n" $_tab "" $_j "$_val";;
        esac
    done
    (( _tab-=2 ))
    printf "%*s}\n" $_tab ""
    return 0
}

My OOP design has not considered objects within objects - except for inherited class. You could create them separately, or make a special constructor like class(). *obj_dump* would need to be modified to detect internal classes to recursively print them.

Oh! and I manually define a ROOT class to simplify class function:

declare -gA ROOT=(    \
  [this]=ROOT         \
  [0]="dispatch ROOT" \
  [dump]=obj_dump     \
  [set]="ROOT_set"    \
  [get]="ROOT_get"    \
)

With a few queue functions I defined some classes like this:

class Queue          \
    in=0 out=0 N=10  \
    dump=obj_dump    \
    add=q_add        \
    empty=q_empty    \
    full=q_full      \
    peek=q_peek      \
    remove=q_remove

class RoughQueue:Queue     \
    N=100                  \
    shove=q_shove          \
    head_drop=q_head_drop

Created some Queue instances and made them work:

class Q:Queue N=1000
$Q add aaa bbb "ccc ddd"
$Q peek X
$Q remove X
printf "X=%s\n" "$X"
$Q remove X
printf "X=%s\n" "$X"
$Q remove X
printf "X=%s\n" "$X"


class R:RoughQueue N=3
$R shove aa bb cc dd ee ff gg hh ii jj
$R dump
2
  • Might work but it's ugly. And totally not what bash is for. Reminds me of Stephane's answer about why not to use shell loops to process text, particularly the section headed "conceptually" that details the difference in purpose between languages like C and bash. unix.stackexchange.com/a/169765/135943
    – Wildcard
    Oct 24, 2015 at 13:23
  • 1
    Might work? It does work, but your point is that, although not wrong, it just is not done. I also did not answer OP question, but if bash is TC then I thought it should be able to process objects. And many have demonstrated this. Nov 22, 2015 at 14:56
7

ksh93t+ is introducing some OO concepts while retaining the bourne/posix shell syntax: http://blog.fpmurphy.com/2010/05/ksh93-using-types-to-create-object-orientated-scripts.html

5

IPython is surprisingly convenient to use.

Standard shell features: job control, readline editing and history, aliases, cat ls cd and pwd, pager integration, running any system command by prefixing it with a ! or enabling %rehashx, command output assignable to a python variable, python values available as shell variables.

Python-specific: reusing results from the last commands, quick access to documentation and source, module reloading, debugger. Some cluster support if you're into that.

That said, running complex pipes isn't done in Python; you'll be using the posix shell as well, just with some glue to pass values to and fro.

2

There're Rush which uses ruby and Psh which is based on perl.

2
  • The website for Rush is no longer live. Here's an archived version, and here's the github repo. Apparently the project was revived here (blog post).
    – waldyrious
    Mar 15, 2018 at 17:44
  • 1
    The s-mage/rush repository, the more recent one, has been archived by the owner. The most recent commits have been done 4 years ago.
    – apaderno
    Jan 22, 2021 at 8:49
2

jq works quite well as such a sort of object-oriented layer.

2

This one is a bit simpler to use and set-up, has named args, etc. https://github.com/uudruid74/bashTheObjects

I'm updating my answer with an example, which follows one of the basic examples given for another answer, but with this syntax. The example program is similar, but you don't have to prefix all the variables with the classname (it knows this as the kindof method shows) and I think the syntax is much simpler!

First, a class file. The defaults for the instance variables are optional and only used if you don't pass these values into the constructor.

class Person
    public show
    public set
    public Name
    public Age
    public Sex
    inst var Name "Saranyan"
    inst var Age 10
    inst var Sex "Male"

Person::Person { :; }
Person::set() { :; }
Person::Name() { println $Name }
Person::Age() { println $Age }
Person::Sex() { println $Sex }
Person::show() {
    Person::Name
    Person::Age
    Person::Sex
}

Now for example usage:

#!/bin/bash
source static/oop.lib.sh

import Person

new Person Christy Name:"Christy" Age:21 Sex:"female"
new Person Evan Name:"Evan" Age:41 Sex:"male"

println "$(Evan.Name) is a $(Evan.Sex) aged $(Evan.Age)"
println "$(Christy.Name) is a $(Christy.Sex) aged $(Christy.Age)"
println "Stats for Evan ..."
Evan.show

assert 'kindof Person Evan'
assert '[ $Evan = $Evan ]'
assert 'kindof Person Christy'
assert '[ $Evan = $Christy ]'

NOTES:

  1. That last assertion will fail. Unlike the above example, the library does not yet support object assignment, but that wouldn't be too hard to add. I'll place it on my TO-DO along with the upcoming container/iterator support.

The import statement is technically not required, but it forces the loading of the class at the given point instead of waiting for the first new, which can help initialize things in the proper order. Note the ease at which you can set multiple instance variables all at once.

There are also debug levels, constructors, destructors, subclassing, and a basic reflection system, and shown is print/println to replace echo (ever try to print a variable that starts with a dash?). Example on github shows it running as a CGI generating HTML from classes.

The library itself (oop.lib.sh) is not so simple (400+ lines, 11K), but you just include it and forget it.

2

You can install PowerShell Core Edition on Linux now. It runs on the cross-platform .NET Core framework which is actively being developed by Microsoft.

1

If someone wants only the basics of object-oriented programming (properties and methods) than a really simple framework would do the trick.

Let's say you want to display the text "Hello World" using objects. First you create an object class which has a property for the text to be displayed and has some methods to set this text and display it. To show how multiple instances of a class can work together I have added two methods for displaying the text : one with NewLine at the end and one without that.

Class definition file : EchoClass.class

# Define properties
<<InstanceName>>_EchoString="Default text for <<InstanceName>>"

# Define methods
function <<InstanceName>>_SetEchoString()
{
  <<InstanceName>>_EchoString=$1
}

function <<InstanceName>>_Echo()
{
  # The -ne parameter tells echo not to add a NewLine at the end (No Enter)
  echo -ne "$<<InstanceName>>_EchoString"
}

function <<InstanceName>>_EchoNL()
{
  echo "$<<InstanceName>>_EchoString"
}

Please note the word "< < InstanceName > >". This will be replaced later to create multiple instances of a class object. Before you can use an instance of an object you need a function which actually creates it. To keep things simple it will be a separate script called : ObjectFramework.lib

# 1st parameter : object instance name
# 2nd parameter : object instance class

function CreateObject()
{
  local InstanceName=$1
  local ObjectClass=$2
  # We will replace all occurences of the text "<<InstanceName>>" in the class file 
  # to the value of the InstanceName variable and store it in a temporary file
  local SedString='s/<<InstanceName>>/'$InstanceName'/g '$ObjectClass'.class'
  local TmpFile=$ObjectClass'_'$InstanceName'.tmp'
  sed $SedString > $TmpFile

  # The file will contain code which defines variables (properties) and functions (methods)
  # with the name we gave to our object instance via the 1st parameter of this function
  # ... we run this code so the variables and functions are actually defined in runtime
  source "$TmpFile"

  # Than remove the temp file as we don't need it any more
  rm "$TmpFile"
}

So now we have a class definition file and a CreateObject function which creates a copy of this file with the text "< < InstanceName > >" replaced to whatever name we want.

Let's use our new object in a script called : HelloWorld.sh (please note that HelloWorld.sh should be executable. The other two files don't need to)

# Define the CreateObject function via the lib file we created
source ObjectFramework.lib

# Create two instances of the EchoClass class
CreateObject MyHello EchoClass
CreateObject MyWorld EchoClass

# Call the SetEchoString method of the two objects. In reality these are 
# just two identical functions named differently and setting different
# variables (remember the <<InstanceName>>_EchoString variable?)
MyHello_SetEchoString "Hello "
MyWorld_SetEchoString "World"

# Finally we call the Echo and EchoNL (NewLine) methods
MyHello_Echo
MyWorld_EchoNL

By running the HelloWorld.sh script it displays the text "Hello World" (and adds a NewLine). Nobody will be impressed much by this result, however we will know this is not that simple as it looks like :)

Happy coding!

1
  • It is better to make complex things simple than to make simple things complex.
    – Wildcard
    Jun 7, 2016 at 19:29
1

This is a object oriented shell based in Python, but it has a sintaxe close of Golang: https://github.com/alexst07/shell-plus-plus

For example, try catch:

try {
  git clone [email protected]:alexst07/shell-plus-plus.git
} catch InvalidCmdException as ex {
  print("git not installed [msg: ", ex, "]")
}

class and operator overload:

class Complex {
  func __init__(r, i) {
    this.r = r
    this.i = i
  }

  func __add__(n) {
    return Complex(n.r + this.r, n.i + this.i)
  }

  func __sub__(n) {
    return Complex(n.r - this.r, n.i - this.i)
  }

  func __print__() {
    return string(this.r) + " + " + string(this.i) + "i"
  }
}

c1 = Complex(2, 3)
c2 = Complex(1, 2)
c = c1 + c2

print(c)

and you can use the commands similar bash:

echo "Test" | cat # simple pipeline
ls src* | grep -e "test" # using glob

# using variables content as command
cip = "ipconfig"
cgrep = ["grep", "-e", "10\..*"]
${cip} | $@{cgrep} # pass an array to command
0

Now, with what objects are you dealing in a shell most of the time? It's files/directories, processes and their interaction. So it should like f1.edit or something like currentFile=f1.c ; .edit ; .compile ; .run. Or d1.search(filename='*.c' string='int \*'). Or p1.stop, p1.bg. That's my understanding of an ooshell.

0
## implemantion of base class
function Class()
{
    base=${FUNCNAME}
    this=${1}
    Class_setCUUID $this
    for method in $(compgen -A function)
    do
        export ${method/#$base\_/$this\_}="${method} ${this}"
    done

}

function copyCUUID()
{
        export ${2}_CUUID=$(echo $(eval "echo \$${1}_CUUID"))

}

function Class_setCUUID()
{
        export ${1}_CUUID=$(uuid)
}

function Class_getCUUID()
{
        echo $(eval "echo \$${2}_CUUID")
}


function Class_setProperty()
{
        export ${1}_${2}=${3}
}

function Class_getProperty()
{
        echo $(eval "echo \$${1}_${2}")
}

function Class_Method()
{
        echo "function ${1}_${2}()
        {
        echo null
        }
        " > /tmp/t.func
        . /tmp/t.func
        rm /tmp/t.func


}

function Class_setMethod()
{
        export ${1}_${2}=${1}_${2}
}


function Class_getMethod()
{
        $(eval "echo \$${1}_${2}")
}


function Class_equals()
{
        base="Class"
        this=${2}

    copyCUUID ${1} ${2}
    for method in $(compgen -A function)
    do
        export ${method/#$base\_/$this\_}="${method} ${1}"
    done


}

just tried of introducing oo concepts to bash based on reference http://hipersayanx.blogspot.in/2012/12/object-oriented-programming-in-bash.html

source ./oobash

Class person
$person_setProperty Name "Saranyan"
$person_setProperty Age 10
$person_setProperty Sex "Male"
function person_show()
{
$person_getProperty Name
$person_getProperty Age
$person_getProperty Sex
}
$person_setMethod show

$person_equals person1
$person1_getMethod show
$person1_equals person3
$person_getCUUID person
$person_getCUUID person1
$person_getCUUID person3
0

Sorry for the short reply but here goes.

hipersayanx has created an article Object Oriented Programming in Bash. Basically he hi-jacked $FUNCNAME,function, compgen, and export to create as close to OOP one can get in bash.

Cool part is it works well and one only needs a few lines of boilerplating to build a class.

Basic parts needed are:

ClassName() {
# A pointer to this Class. (2)
base=$FUNCNAME
this=$1

# Inherited classes (optional).
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.
export ${this}_x=$2
export ${this}_y=$3
export ${this}_z=$4

# Declare methods.
for method in $(compgen -A function); do
    export ${method/#$base\_/$this\_}="${method} ${this}"
done
}

function ClassName_MethodName()
{
#base is where the magic happens, its what holds the class name
base=$(expr "$FUNCNAME" : '\([a-zA-Z][a-zA-Z0-9]*\)')
this=$1

x=$(eval "echo \$${this}_x")

echo "$this ($x)"
}

Usage:

# Create a new Class Instance
ClassName 'instanceName' $param1 $param2

$instanceName_method

Now, I have used this myself in my AuditOps project and hipersayanx has further details on how this actually works on his site. Fare warning though this is very bashism will not work with anything older than bash 4.0 and can lead to a headache in debugging. While personally I would like to see most of the boiler plating redone as a class itself.

Its always wiser to use a serious OOP scripting language like perl, ruby, and python when better suited to your project. However in my honest option its worth the time and effort when maintaining modular bash scripts to utilize this method of OOP in bash.

0

I'm developing on GitHub a function that works just like a HashMap Object, shell_map.

In order to create "HashMap instances" this function is able create copies of itself under different names. Each new function copy will have a different $FUNCNAME variable. $FUNCNAME then is used to create a namespace for each Map instance.

The map keys are global variables, in the form $FUNCNAME_DATA_$KEY, where $KEY is the key added to the Map. These variables are dynamic variables.

Bellow I'll put a simplified version of it so you can use as example.

#!/bin/bash

shell_map () {
    local METHOD="$1"

    case $METHOD in
    new)
        local NEW_MAP="$2"

        # loads shell_map function declaration
        test -n "$(declare -f shell_map)" || return

        # declares in the Global Scope a copy of shell_map, under a new name.
        eval "${_/shell_map/$2}"
    ;;
    put)
        local KEY="$2"  
        local VALUE="$3"

        # declares a variable in the global scope
        eval ${FUNCNAME}_DATA_${KEY}='$VALUE'
    ;;
    get)
        local KEY="$2"
        local VALUE="${FUNCNAME}_DATA_${KEY}"
        echo "${!VALUE}"
    ;;
    keys)
        declare | grep -Po "(?<=${FUNCNAME}_DATA_)\w+((?=\=))"
    ;;
    name)
        echo $FUNCNAME
    ;;
    contains_key)
        local KEY="$2"
        compgen -v ${FUNCNAME}_DATA_${KEY} > /dev/null && return 0 || return 1
    ;;
    clear_all)
        while read var; do  
            unset $var
        done < <(compgen -v ${FUNCNAME}_DATA_)
    ;;
    remove)
        local KEY="$2"
        unset ${FUNCNAME}_DATA_${KEY}
    ;;
    size)
        compgen -v ${FUNCNAME}_DATA_${KEY} | wc -l
    ;;
    *)
        echo "unsupported operation '$1'."
        return 1
    ;;
    esac
}

Usage:

shell_map new credit
credit put Mary 100
credit put John 200
for customer in `credit keys`; do 
    value=`credit get $customer`       
    echo "customer $customer has $value"
done
credit contains "Mary" && echo "Mary has credit!"
4
  • You seem to have misunderstood how command substitution works. It is replaced with the output of the command contained within backticks, not replaced with the return status. In other words your code doesn't do what you think it does.
    – Wildcard
    Jun 7, 2016 at 19:33
  • Aha. You are correct. My apologies. However, it would be much clearer to use carp "Some error message"; return instead.
    – Wildcard
    Jun 8, 2016 at 4:53
  • Or [ -z "$KEY" ] && { carp "some message"; return;} No need for a subshell. But actually that looks like an actual candidate for an if ... elif ... elif ... else ... fi construct—which isn't often the best choice, but probably is, here. :) (Or maybe a case switch.)
    – Wildcard
    Jun 8, 2016 at 21:21
  • @Wildcard I edited the answer. Now our discussion about carp and backticks makes no sense. let's delete this conversation? Jun 9, 2016 at 4:48
0

Plumbum is a Python-like shell language. It packages shell like syntax with Python making the experience object-orientated.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .