*nix 的面向对象 shell

Rob*_*cio 43 shell scripting bash architecture

前言:我喜欢 bash 并且无意开始任何形式的争论或圣战,希望这不是一个非常幼稚的问题。

这个问题与超级用户上的这篇文章有些相关,但我认为 OP 并不真正知道他在问什么。我在 FreeBSD、linux、OS X 和 Windows 上的 cygwin 上使用 bash。我最近在 Windows 上使用 PowerShell 也有丰富的经验。

是否有用于 *nix 的 shell,已经可用或正在开发中,它与 bash 兼容,但在混合中添加了一层面向对象的脚本?我所知道的唯一接近的是 python 控制台,但据我所知,它不提供对标准 shell 环境的访问。例如,我不能只是cd ~and ls,然后chmod +x file在 python 控制台中。我将不得不使用 python 来执行这些任务,而不是标准的 unix 二进制文件,或者使用 python 代码调用二进制文件。

这样的壳存在吗?

Gil*_*il' 46

我可以想到 shell 中的三个理想特性:

  • 交互式可用性:常用命令应该可以快速输入;完成;...
  • 编程:数据结构;并发(工作,管道,...);...
  • 系统访问:使用文件、进程、窗口、数据库、系统配置,...

Unix shell 倾向于专注于交互方面,并将大部分系统访问和一些编程分包给外部工具,例如:

  • bc简单数学
  • 密码学的openssl
  • sed , awk等用于文本处理
  • nc用于基本 TCP/IP 网络
  • ftp 用于 FTP
  • mail, Mail,mailx等用于基本电子邮件
  • cron 用于计划任务
  • wmctrl用于基本的 X 窗口操作
  • KDE 3.x 库的dcop
  • 用于各种系统信息和配置任务的dbus工具(dbus-*qdbus)(包括现代桌面环境,如 KDE?4)

通过使用正确的参数或管道输入调用命令,可以完成很多很多事情。这是一种非常强大的方法——每个任务最好有一个工具可以很好地完成它,而不是一个程序可以完成所有事情但很糟糕——但它确实有其局限性。

unix shell 的一个主要限制,我怀疑这就是您对“面向对象的脚本”要求所追求的,是它们不擅长将信息从一个命令保留到下一个命令,或者以更奇特的方式组合命令一个管道。特别是,程序间通信是基于文本的,因此应用程序只有在以兼容的方式序列化其数据时才能组合。这既是福也是祸:一切都是文本的方法可以轻松快速地完成简单的任务,但为更复杂的任务增加了障碍。

交互式可用性也与程序可维护性背道而驰。交互式程序应该很短,需要很少的引用,不要用变量声明或打字等来打扰你。 可维护的程序应该是可读的(所以没有很多缩写),应该是可读的(所以你不必怀疑一个词是字符串、函数名、变量名等),应该有一致性检查,如变量声明和类型等。

总之,shell 是一个难以达成的妥协。好的,到此结束咆哮部分,进入示例。


  • Perl的壳牌(PSH) “结合了Unix外壳的互动性用Perl的力量”。可以用 shell 语法输入简单的命令(甚至是管道);其他一切都是 Perl。该项目已经很长时间没有开发了。它是可用的,但还没有达到我考虑在纯 Perl(用于脚本)或纯 shell(用于交互或用于脚本)上使用它的地步。

  • IPython是一种改进的交互式 Python 控制台,特别针对数值和并行计算。这是一个相对年轻的项目。

  • irb(交互式 ruby​​)是 Python 控制台的 Ruby 等价物。

  • scsh是一种方案实现(即一种体面的编程语言),具有传统上在 unix shell(字符串、进程、文件)中发现的系统绑定类型。然而,它的目标并不是用作交互式 shell。

  • zsh是一个改进的交互式 shell。它的强项是交互性(命令行版本、完成、使用简洁但神秘的语法完成的常见任务)。它的编程功能不是很好(与 ksh 相当),但它带有许多用于终端控制、正则表达式、网络等的库。

  • fish是 unix 风格的 shell 的一个干净的开始。它没有更好的编程或系统访问功能。因为它破坏了与 sh 的兼容性,所以它有更多的空间来发展更好的功能,但这并没有发生。


附录:unix 工具箱的另一部分将许多东西视为文件:

  • 大多数硬件设备都可以作为文件访问。
  • 在 Linux 下,/sys提供更多的硬件和系统控制。
  • 在许多 Unix 变体上,进程控制可以通过/proc文件系统完成。
  • FUSE使编写新文件系统变得容易。已经存在用于动态转换文件格式、通过各种网络协议访问文件、查看档案内部等的现有文件系统。

也许 unix shell 的未来不是通过命令更好的系统访问(以及更好的控制结构来组合命令),而是通过文件系统更好的系统访问(它们的组合有些不同——我认为我们还没有弄清楚关键习语是什么(比如壳管)是)。


phi*_*urn 14

您不需要太多的 bash 代码来在 bash 中实现类或对象。

说,100 行。

Bash 具有关联数组,可用于实现具有继承、方法和属性的简单对象系统。

因此,您可能会像这样定义一个类:

class Queue N=10 add=q_add remove=q_remove
Run Code Online (Sandbox Code Playgroud)

创建这个队列的一个实例可能是这样完成的:

class Q:Queue N=100
Run Code Online (Sandbox Code Playgroud)

或者

inst Q:Queue N=100
Run Code Online (Sandbox Code Playgroud)

因为类是用数组实现的,所以classinst实际上是同义词 - 有点像在 javascript 中。

可以像这样将项目添加到此队列中:

$Q add 1 2 aaa bbb "a string"
Run Code Online (Sandbox Code Playgroud)

将项目移入变量 X 可能会像这样完成:

$Q remove X
Run Code Online (Sandbox Code Playgroud)

对象的转储结构可以这样完成:

$Q dump
Run Code Online (Sandbox Code Playgroud)

这将返回如下内容:

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=
    }
Run Code Online (Sandbox Code Playgroud)

类是使用类函数创建的,如下所示:

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"
}
Run Code Online (Sandbox Code Playgroud)

注意:定义新类或实例时,您可以覆盖任何成员值或函数。

Bash 关联数组有一个奇怪的地方,它使这项工作非常顺利:$Q[0]} 与 $Q 相同。这意味着我们可以使用数组名来调用方法调度函数:

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

不利的一面是我不能将 [0] 用于数据,因此我的队列(在这种情况下)从索引 = 1 开始。或者,我可以使用像“q+0”这样的关联索引。

获取设置成员,您可以执行以下操作:

# 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
}
Run Code Online (Sandbox Code Playgroud)

为了转储一个对象结构,我做了这个:

注意:这对于 bash 中的 OOP 不是必需的,但是很高兴看到对象是如何制作的。

# 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
}
Run Code Online (Sandbox Code Playgroud)

我的 OOP 设计没有考虑对象中的对象——除了继承的类。你可以单独创建它们,或者创建一个像 class() 这样的特殊构造函数。*obj_dump* 需要修改以检测内部类以递归打印它们。

哦!我手动定义了一个 ROOT 类来简化功能:

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

通过一些队列函数,我定义了一些这样的类:

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
Run Code Online (Sandbox Code Playgroud)

创建了一些 Queue 实例并使它们工作:

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
Run Code Online (Sandbox Code Playgroud)


jll*_*gre 7

ksh93t+ 在保留 bourne/posix shell 语法的同时引入了一些 OO 概念:http : //blog.fpmurphy.com/2010/05/ksh93-using-types-to-create-object-orientated-scripts.html


Tob*_*obu 5

IPython使用起来非常方便。

标准 shell 功能:作业控制、readline 编辑和历史记录、别名cat ls cdpwd寻呼机集成,通过在任何系统命令前加上 a!或enable 来运行任何系统命令,%rehashx可分配给 python 变量的命令输出,可作为 shell 变量使用的 python 值。

特定于 Python 的:重用上一个命令的结果、快速访问文档和源代码、模块重新加载、调试器。如果您愿意,可以提供一些集群支持。

也就是说,运行复杂的管道不是在 Python 中完成的。您还将使用 posix shell,只需使用一些胶水来回传递值。