Shell orientado a objetos para * nix

38

Prefácio: Eu amo bash e não tenho intenção de iniciar qualquer tipo de argumento ou guerra santa, e espero que essa não seja uma pergunta extremamente ingênua.

Essa pergunta está um pouco relacionada a este post sobre superusuário, mas não acho que o OP realmente sabia o que estava pedindo. Eu uso o bash no FreeBSD, linux, OS X e cygwin no Windows. Também tive uma vasta experiência recentemente com o PowerShell no Windows.

Existe um shell para * nix, já disponível ou em andamento, compatível com o bash, mas que adiciona uma camada de script orientado a objetos ao mix? A única coisa que sei disso se aproxima é o console python, mas, tanto quanto posso dizer, não fornece acesso ao ambiente shell padrão. Por exemplo, eu não posso apenas cd ~e ls, em seguida, chmod +x filedentro do console python. Eu precisaria usar o python para executar essas tarefas, em vez dos binários unix padrão, ou chamar os binários usando o código python.

Existe tal shell?

Robert S Ciaccio
fonte
3
Pash, mas é muito mais parecido com o PowerShell do que com o Bash.
ephemient
1
@hemhemient talvez você deva escrever uma resposta para pash ... embora eu não saiba nada sobre isso, iirc, powershell é um shell OO.
Xenoterracide
4
Ei, você deve verificar o ipython . Se você digitar uma expressão que não faz sentido como python, ela tentará mapeá-la para um comando shell. Por exemplo, coisas como cd ~seguidas de lsobras como no Bash. Você também pode atribuir saída a variáveis ​​Python (listas de linhas. Mais ou menos) com comandos como listing = !ls.
intuited
@intuited: awesome, eu vou dar uma olhada
Robert S Ciaccio
1
@ intuited: O iPython tem sido muito bom para as coisas que eu quero fazer, obrigado!
Robert S Ciaccio

Respostas:

43

Eu posso pensar em três características desejáveis ​​em um shell:

  • Usabilidade interativa: comandos comuns devem ser rápidos para digitar; conclusão; ...
  • Programação: estruturas de dados; concorrência (trabalhos, canalizações, ...); ...
  • Acesso ao sistema: trabalhando com arquivos, processos, janelas, bancos de dados, configuração do sistema, ...

Os shells Unix tendem a se concentrar no aspecto interativo e subcontratam a maior parte do acesso ao sistema e parte da programação para ferramentas externas, como:

  • bc para matemática simples
  • openssl para criptografia
  • sed , awk e outros para processamento de texto
  • nc para redes TCP / IP básicas
  • ftp para FTP
  • mail, Mail, mailx, Etc. para o e-mail básico
  • cron para tarefas agendadas
  • wmctrl para manipulação básica de janelas X
  • dcop para bibliotecas KDE ≤3.x
  • Ferramentas dbus ( dbus-*ou qdbus ) para várias tarefas de informações e configuração do sistema (incluindo ambientes de desktop modernos, como o KDE ≥4)

Muitas coisas podem ser feitas invocando um comando com os argumentos corretos ou com a entrada canalizada. Essa é uma abordagem muito poderosa - é melhor ter uma ferramenta por tarefa que faça isso bem do que um único programa que faz tudo menos que mal - mas tem suas limitações.

Uma grande limitação dos shells unix, e suspeito que é isso que você procura com seu requisito de "script orientado a objetos", é que eles não são bons em reter informações de um comando para o próximo ou combinar comandos de maneiras mais sofisticadas do que um gasoduto. Em particular, a comunicação entre programas é baseada em texto; portanto, os aplicativos só podem ser combinados se eles serializarem seus dados de maneira compatível. Isso é uma bênção e uma maldição: a abordagem "tudo é texto" facilita a execução de tarefas simples rapidamente, mas aumenta a barreira para tarefas mais complexas.

A usabilidade interativa também funciona bastante contra a manutenção do programa. Os programas interativos devem ser curtos, exigir poucas citações, não incomodá-lo com declarações variáveis ​​ou digitação, etc. é uma string, um nome de função, um nome de variável etc.), deve ter verificações de consistência, como declarações de variáveis ​​e digitação, etc.

Em resumo, um shell é um compromisso difícil de alcançar. Ok, isso termina a seção de retórica, para os exemplos.


  • O Perl Shell (psh) "combina a natureza interativa de um shell Unix com o poder do Perl". Comandos simples (até pipelines) podem ser inseridos na sintaxe do shell; tudo o resto é Perl. O projeto não está em desenvolvimento há muito tempo. É utilizável, mas não chegou ao ponto em que eu consideraria usá-lo sobre o Perl puro (para script) ou o shell puro (interativamente ou para script).

  • O IPython é um console Python interativo aprimorado, especialmente direcionado à computação numérica e paralela. Este é um projeto relativamente jovem.

  • irb (ruby interativo) é o equivalente em Ruby do console do Python.

  • scsh é uma implementação de esquema (isto é, uma linguagem de programação decente) com o tipo de ligações do sistema tradicionalmente encontradas em shells unix (strings, processos, arquivos). No entanto, não pretende ser utilizável como um shell interativo.

  • O zsh é um shell interativo aprimorado. Seu ponto forte é a interatividade (edição de linha de comando, conclusão, tarefas comuns realizadas com sintaxe concisa mas enigmática). Seus recursos de programação não são tão bons (comparáveis ​​ao ksh), mas vêm com várias bibliotecas para controle de terminal, regexps, rede etc.

  • fish é um começo limpo em uma concha no estilo unix. Não possui recursos melhores de programação ou acesso ao sistema. Por quebrar a compatibilidade com o sh, ele tem mais espaço para desenvolver recursos melhores, mas isso não aconteceu.


Adendo: outra parte da caixa de ferramentas unix está tratando muitas coisas como arquivos:

  • A maioria dos dispositivos de hardware é acessível como arquivos.
  • No Linux, /sysfornece mais controle de hardware e sistema.
  • Em muitas variantes do unix, o controle do processo pode ser feito através do /procsistema de arquivos.
  • O FUSE facilita a criação de novos sistemas de arquivos. Já existem sistemas de arquivos para converter formatos de arquivos em tempo real, acessar arquivos através de vários protocolos de rede, procurar dentro de arquivos etc.

Talvez o futuro dos shells unix não seja um melhor acesso ao sistema por meio de comandos (e melhores estruturas de controle para combinar comandos), mas um melhor acesso ao sistema por meio de sistemas de arquivos (que se combinam de maneira um pouco diferente - acho que não descobrimos quais são os principais idiomas (como o tubo da carcaça) ainda estão).

Gilles 'SO- parar de ser mau'
fonte
1
Você bate na unha logo depois de dizer "Eu suspeito que é isso que você está buscando". A principal razão pela qual estou fazendo essa pergunta é que eu amo ter o poder das ferramentas unix, mas a interação baseada em texto entre programas definitivamente 'cria uma barreira para tarefas mais complexas'. Passei o suficiente dos meus dias de programação escrevendo analisadores de texto :) Acho que essa é uma resposta muito bem pensada. Chega ao cerne da questão e da complexidade do assunto. Eu gostaria de poder votar duas vezes: P
Robert S Ciaccio
1
+1 para ipython, embora eu não tenha ideia do que o OP quer fazer.
Falmarri
1
Esta é uma ótima resposta: sinceramente, acho que as sementes de uma interessante tese de doutorado estão aqui.
Ziggy
1
@RobertSCiaccio Esta resposta acabou de ser vinculada em um post recente, e seu comentário sobre análise de texto me fez pensar ... se a análise de texto é suficiente para realizar suas "tarefas complexas", então você não poderia ter um pequeno script ou programa que implementa e usá-lo como algum tipo de função em seus scripts bash? Apenas um pensamento, não tenho muita experiência em scripts bash para falar.
Oxwivi
1
@onlyanegg De que maneira os peixes podem ser considerados “orientados a objetos”? O peixe visa principalmente ser mais simples. Existe alguma maneira de ser mais poderoso que as alternativas?
Gilles 'SO- stop be evil'
13

Você não precisa de muito código bash para implementar classes ou objetos no bash.

Digamos, 100 linhas.

O Bash possui matrizes associativas que podem ser usadas para implementar um sistema de objetos simples com herança, métodos e propriedades.

Portanto, você pode definir uma classe como esta:

class Queue N=10 add=q_add remove=q_remove

A criação de uma instância desta fila pode ser feita assim:

class Q:Queue N=100

ou

inst Q:Queue N=100

Como as classes são implementadas com uma matriz, classe e inst são realmente sinônimos - como em javascript.

Adicionar itens a essa fila pode ser feito da seguinte maneira:

$Q add 1 2 aaa bbb "a string"

A remoção de itens em uma variável X pode ser feita assim:

$Q remove X

E a estrutura de dumping de um objeto pode ser feita assim:

$Q dump

O que retornaria algo como isto:

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=
    }

As classes são criadas usando uma função de classe como esta:

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"
}

NOTA: Ao definir uma nova classe ou instância, você pode substituir qualquer valor ou função de membro.

As matrizes associativas Bash têm uma peculiaridade que faz com que isso funcione perfeitamente: $ Q [0]} é idêntico a $ Q. Isso significa que podemos usar o nome do array para chamar uma função de despacho de método:

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

Uma desvantagem é que eu não posso usar [0] para dados, então minhas filas (neste caso) começam no índice = 1. Alternativamente, eu poderia ter usado índices associativos como "q + 0".

Para obter e definir membros, você pode fazer algo assim:

# 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
}

E para despejar uma estrutura de objeto, eu fiz isso:

NOTA: Isso não é necessário para o OOP no bash, mas é bom ver como os objetos são criados.

# 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
}

Meu design OOP não considerou objetos dentro de objetos - exceto para a classe herdada. Você pode criá-los separadamente ou criar um construtor especial como class (). * obj_dump * precisaria ser modificado para detectar classes internas para imprimi-las recursivamente.

Oh! e eu defino manualmente uma classe ROOT para simplificar a função da classe :

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

Com algumas funções de fila, defini algumas classes como esta:

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

Criou algumas instâncias da fila e as fez funcionar:

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
philcolbourn
fonte
Pode funcionar, mas é feio . E totalmente não bashé para isso. Lembra-me da resposta de Stephane sobre por que não usar loops de shell para processar texto, particularmente a seção "conceitualmente", que detalha a diferença de propósito entre idiomas como C e bash. unix.stackexchange.com/a/169765/135943
curinga
1
Pode funcionar? Funciona, mas o seu argumento é que, embora não esteja errado, simplesmente não está feito. Também não respondi à pergunta do OP, mas se o bash for TC, pensei que deveria poder processar objetos. E muitos demonstraram isso.
philcolbourn
5

O IPython é surpreendentemente conveniente de usar.

Recursos padrão do shell: controle de tarefas, edição e histórico de linhas de leitura, aliases cat ls cde pwdintegração de pager, executando qualquer comando do sistema prefixando-o com um !ou ativando %rehashx, saída de comando atribuível a uma variável python, valores python disponíveis como variáveis ​​do shell.

Específico para Python: reutilizando resultados dos últimos comandos, acesso rápido à documentação e fonte, recarregamento de módulos, depurador. Algum suporte de cluster, se você gosta disso.

Dito isto, a execução de pipes complexos não é feita no Python; você também usará o shell posix, apenas com um pouco de cola para passar valores de um lado para o outro.

Tobu
fonte
2

jq funciona muito bem como uma camada orientada a objetos.

Abbafei
fonte
2

Este é um pouco mais simples de usar e configurar, nomeou args etc. https://github.com/uudruid74/bashTheObjects

Estou atualizando minha resposta com um exemplo, que segue um dos exemplos básicos dados para outra resposta, mas com esta sintaxe. O programa de exemplo é semelhante, mas você não precisa prefixar todas as variáveis ​​com o nome da classe (ele sabe disso como mostra o método kindof ) e acho que a sintaxe é muito mais simples!

Primeiro, um arquivo de classe. Os padrões para as variáveis ​​de instância são opcionais e usados ​​apenas se você não passar esses valores para o construtor.

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
}

Agora, por exemplo, uso:

#!/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 ]'

NOTAS:

  1. Essa última afirmação falhará. Ao contrário do exemplo acima, a biblioteca ainda não suporta a atribuição de objetos, mas isso não seria muito difícil de adicionar. Vou colocá-lo na minha lista de tarefas juntamente com o próximo suporte a contêiner / iterador.

Tecnicamente, a instrução de importação não é necessária, mas força o carregamento da classe no ponto especificado, em vez de aguardar a primeira novidade , o que pode ajudar a inicializar as coisas na ordem correta. Observe a facilidade com que você pode definir várias variáveis ​​de instância de uma só vez.

Também existem níveis de depuração, construtores, destruidores, subclassificação e um sistema de reflexão básico , e é mostrado print / println para substituir o eco (tente imprimir uma variável que comece com um traço?). O exemplo no github mostra como sendo executado como CGI gerando HTML a partir de classes.

A biblioteca em si (oop.lib.sh) não é tão simples (mais de 400 linhas, 11K), mas você a inclui e a esquece.

Evan Langlois
fonte
2

Você pode instalar o PowerShell Core Edition no Linux agora. Ele é executado na estrutura .NET Core de plataforma cruzada, que está sendo ativamente desenvolvida pela Microsoft.

Trevor Sullivan
fonte
1

Se alguém quiser apenas o básico da programação orientada a objetos (propriedades e métodos), uma estrutura realmente simples seria suficiente.

Digamos que você queira exibir o texto "Hello World" usando objetos. Primeiro, você cria uma classe de objeto que possui uma propriedade para o texto a ser exibido e possui alguns métodos para definir e exibi-lo. Para mostrar como várias instâncias de uma classe podem funcionar juntas, adicionei dois métodos para exibir o texto: um com NewLine no final e outro sem isso.

Arquivo de definição de classe: 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"
}

Observe a palavra "<<Nome da instância>>". Isso será substituído posteriormente para criar várias instâncias de um objeto de classe. Antes de poder usar uma instância de um objeto, você precisa de uma função que realmente o crie. Para simplificar, será um script separado chamado: 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"
}

Portanto, agora temos um arquivo de definição de classe e uma função CreateObject que cria uma cópia desse arquivo com o texto "<<InstanceName>>" substituído pelo nome que desejarmos.

Vamos usar nosso novo objeto em um script chamado: HelloWorld.sh (observe que HelloWorld.sh deve ser executável. Os outros dois arquivos não precisam)

# 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

Ao executar o script HelloWorld.sh, ele exibe o texto "Hello World" (e adiciona um NewLine). Ninguém ficará impressionado com este resultado, no entanto, saberemos que isso não é tão simples quanto parece :)

Feliz codificação!

vandor76
fonte
É melhor simplificar as coisas complexas do que complexar as coisas simples.
Curinga
1

Este é um shell orientado a objeto baseado em Python, mas possui uma sintaxe próxima ao Golang: https://github.com/alexst07/shell-plus-plus

Por exemplo, tente pegar:

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

sobrecarga de classe e operador:

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)

e você pode usar os comandos bash semelhantes:

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
Alex
fonte
0

Agora, com quais objetos você está lidando com um shell na maioria das vezes? São arquivos / diretórios, processos e sua interação. Então deve gostar f1.editou algo assim currentFile=f1.c ; .edit ; .compile ; .run. Or d1.search(filename='*.c' string='int \*'). Ou p1.stop, p1.bg. Essa é a minha compreensão de um Ooshell.

ott--
fonte
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


}

apenas tentei introduzir oo conceitos para o bash com base na referência 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
Aravamudhan saranyan
fonte
0

Desculpe pela resposta curta, mas aqui vai.

hipersayanx criou um artigo Programação Orientada a Objetos no Bash . Basicamente, ele oi-levantado $FUNCNAME, function, compgen, e exportpara criar o mais próximo possível OOP pode-se entrar em bash.

Parte legal é que funciona bem e é preciso apenas algumas linhas de caldeira para construir uma classe.

As peças básicas necessárias são:

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)"
}

Uso:

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

$instanceName_method

Agora, eu mesmo usei isso no meu projeto AuditOps e o hipersayanx tem mais detalhes sobre como isso realmente funciona em seu site. O aviso de tarifa, embora isso seja muito básico, não funcionará com nada mais antigo que o bash 4.0 e pode causar dor de cabeça na depuração. Enquanto pessoalmente, eu gostaria de ver a maior parte do revestimento da caldeira refeito como uma classe em si.

É sempre mais sensato usar uma linguagem de script OOP séria, como perl, ruby ​​e python, quando melhor se adequar ao seu projeto. No entanto, na minha opção honesta, vale a pena o tempo e o esforço ao manter scripts bash modulares para utilizar esse método de OOP no bash.

Dwight Spencer
fonte
0

Estou desenvolvendo no GitHub uma função que funciona exatamente como um objeto HashMap , shell_map .

Para criar " instâncias do HashMap ", essa função pode criar cópias de si mesma com nomes diferentes. Cada nova cópia de função terá uma variável $ FUNCNAME diferente. $ FUNCNAME é usado para criar um espaço para nome para cada instância do Mapa.

As chaves do mapa são variáveis ​​globais, no formato $ FUNCNAME_DATA_ $ KEY, onde $ KEY é a chave adicionada ao mapa. Essas variáveis ​​são variáveis ​​dinâmicas .

Abaixo vou colocar uma versão simplificada para que você possa usar como exemplo.

#!/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
}

Uso:

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!"
Bruno Negrão Zica
fonte
Você parece ter entendido mal como funciona a substituição de comandos. Ele é substituído pela saída do comando contido nos backticks, não substituído pelo status de retorno . Em outras palavras, seu código não faz o que você pensa.
Curinga
Aha. Você está certo. Me desculpe. No entanto, seria muito mais claro usá-lo carp "Some error message"; return.
Curinga
Ou [ -z "$KEY" ] && { carp "some message"; return;} Não há necessidade de um subshell. Mas, na verdade, isso parece um candidato real a uma if ... elif ... elif ... else ... ficonstrução - o que geralmente não é a melhor escolha, mas provavelmente é aqui. :) (Ou talvez uma troca de maiúsculas e minúsculas).
Wildcard
@Wildcard Eu editei a resposta. Agora, nossa discussão sobre carpas e retalhos não faz sentido. vamos excluir esta conversa?
Bruno Negrão Zica
0

Plumbum é uma linguagem shell semelhante ao Python. Ele empacota shell como sintaxe com o Python, tornando a experiência orientada a objetos.

joshlk
fonte