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.
Esta pergunta está um pouco relacionada a este post no 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 padrão do Shell. Por exemplo, não posso simplesmente cd ~
e ls
, então chmod +x file
dentro do console python console. Eu precisaria usar python para executar essas tarefas, em vez dos binários unix padrão, ou chame os binários usando python.
Existe tal Shell?
Posso pensar em três recursos desejáveis em um Shell:
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:
ftp
para FTPmail
, Mail
, mailx
, etc. para email básicocron
para tarefas agendadasdbus-*
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. Programas que podem ser mantidos devem ser legíveis (para que não haja muitas abreviações), devem ser legíveis (para que você não precise se perguntar se um Word vazio é 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 Shell Perl (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).
IPython é um console interativo aprimorado Python console, especialmente direcionado à computação numérica e paralela). Este é um projeto relativamente jovem.
irb (Ruby interativo) é o Ruby equivalente ao Python.
scsh é uma implementação de esquema (ou seja, 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.
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 (a par do ksh), mas vêm com várias bibliotecas para controle de terminal, regexps, rede, etc.
fish é um começo limpo em um Shell 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:
/sys
fornece mais controle de hardware e sistema./proc
sistema de arquivo.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 - não acho que tenhamos descoberto quais são os principais idiomas (como o tubo Shell) ainda estão).
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, class 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 "[email protected]" "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 "[email protected]"
}
Um lado negativo é 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 "[email protected]"; 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 "[email protected]"; do
_var=${_exp%%=*}
_key=${_exp#*=}
eval $_var=\"\${$_this[$_key]}\"
done
}
E para despejo uma estrutura de objeto, eu fiz isso:
NOTA: Isso não é necessário para 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 class:
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
o ksh93t + está introduzindo alguns conceitos OO enquanto mantém a sintaxe do shell bourne/posix: http://blog.fpmurphy.com/2010/05/ksh93-using-types-to- create-object-orientated-scripts.html
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
cd
e pwd
, integração de pager, executando qualquer comando do sistema, prefixando-o com um !
ou habilitando %rehashx
, saída do comando atribuível a uma variável python python, 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 posix Shell, apenas com cola para passar valores de um lado para o outro.
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 I pense 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:
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 o primeiro novo , 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, subclassing e um sistema básico reflection, 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ê apenas a inclui e esquece.
Você pode instalar PowerShell Core Edition no Linux agora. Ele é executado na estrutura .NET Core de plataforma cruzada, que está sendo ativamente desenvolvida pela Microsoft.
jq
funciona muito bem como uma espécie de camada orientada a objetos.
Este é um Shell orientado a objetos 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 [email protected]: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} | [email protected]{cgrep} # pass an array to command
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 "<<InstanceName>>". 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!
## 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 bash com base em 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
Plumbum é uma linguagem Shell semelhante ao Python. Ele empacota a sintaxe do Shell com Python tornando a experiência orientada a objetos.
Agora, com quais objetos você lida com um Shell na maioria das vezes? São arquivos/diretórios, processos e sua interação. Portanto, deve gostar de f1.edit
Ou algo como currentFile=f1.c ; .edit ; .compile ; .run
. Ou d1.search(filename='*.c' string='int \*')
. Ou p1.stop
, p1.bg
. Essa é a minha compreensão de um Ooshell.
Desculpe pela resposta curta, mas aqui vai.
hipersayanx criou um artigo Programação Orientada a Objetos no Bash . Basicamente, ele chocou $FUNCNAME
, function
, compgen
e export
para criar o mais próximo de OOP pode-se entrar no 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 séria OOP como Perl, Ruby e python quando for mais adequada ao seu projeto. No entanto, na minha opção honesta, vale a pena tempo e esforço ao manter scripts bash modulares para utilizar este método de OOP no bash.
Estou desenvolvendo no GitHub uma função que funciona como um Objeto HashMap , Shell_map .
Para criar " instâncias do HashMap ", esta 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!"