Bash实用程序脚本库

时间:2012-07-06 20:53:53

标签: bash

是否存在bash函数的常用(或不公平使用)实用程序“库”?像Apache commons-lang for Java这样的东西。 Bash无处不在,在扩展库领域似乎奇怪地被忽略了。

6 个答案:

答案 0 :(得分:35)

bash的图书馆在那里,但不常见。 bash库稀缺的原因之一是功能的限制。我相信这些限制最好在“Greg's Bash Wiki”中解释:

功能。 Bash的“功能”有几个问题:

  • 代码可重用性:Bash函数不返回任何内容;他们只生产输出流。捕获该流并将其分配给变量或将其作为参数传递的每种合理方法都需要SubShell,这会破坏对外部作用域的所有赋值。 (另请参阅BashFAQ / 084以了解从函数中检索结果的技巧。)因此,可重用函数的库是不可行的,因为您不能要求函数将其结果存储在名称作为参数传递的变量中(除了通过执行eval后空翻)。

  • 范围:Bash有一个简单的局部范围系统,大致类似于“动态范围”(例如Javascript,elisp)。函数查看其调用者的本地(如Python的“非本地”关键字),但无法访问调用者的位置参数(如果启用了extdebug,则通过BASH_ARGV除外)。除非您诉诸奇怪的命名规则以使冲突不太可能,否则无法保证可重用函数不受命名空间冲突的影响。如果实现期望处理来自第n-3帧的变量名的函数,这可能已被n-2处的可重用函数覆盖,则这尤其成问题。 Ksh93可以通过使用“function name {...}”语法声明函数来使用更常见的词法范围规则(Bash不能,但无论如何都支持这种语法)。

  • 闭包:在Bash中,函数本身总是全局的(具有“文件范围”),因此没有闭包。函数定义可以嵌套,但这些不是闭包,尽管它们看起来非常相似。函数不是“可通过的”(第一类),并且没有匿名函数(lambdas)。事实上,没有什么是“可以通过的”,特别是不是数组。 Bash严格使用call-by-value语义(魔术别名除外)。

  • 还有许多并发症涉及:子壳;出口功能; “功能崩溃”(定义或重新定义其他功能或自身的功能);陷阱(及其继承);以及函数与stdio交互的方式。不要因为不理解这一切而咬新手。 Shell函数完全被伪造。

来源:http://mywiki.wooledge.org/BashWeaknesses

shell“库”的一个示例是基于Redhat的系统上的/etc/rc.d/functions。此文件包含sysV init脚本中常用的函数。

答案 1 :(得分:14)

我在这里看到一些好消息和不良信息。让我分享我所知道的,因为bash是我在工作中使用的主要语言(我们构建库......)。 一般来说,Google对bash脚本有一个不错的写作,我认为这是一个很好的阅读:https://google.github.io/styleguide/shell.xml

首先让我说你不应该想到一个bash库,因为你在其他语言中使用库。 必须执行某些实践才能使bash库变得简单,有条理,最重要的是可重用。

除了打印的字符串和函数的退出状态(0-255)之外,没有从bash函数返回任何内容的概念。 这里存在预期的局限性和学习曲线,特别是如果您习惯于高级语言的功能。 一开始可能很奇怪,如果你发现自己处于字符串不切割它的情况下,你会想要利用外部工具,如jq。 如果jq(或类似的东西)可用,你可以开始让你的函数打印格式化的输出进行解析&像对象,数组等一样使用

功能声明

有两种方法可以在bash中声明一个函数。 一个在你当前的shell中运行,我们称之为Fx0。 一个产生子壳进行操作,我们称之为Fx1。 以下是他们如何宣布的例子:

Fx0(){ echo "Hello from $FUNCNAME"; }
Fx1()( echo "Hello from $FUNCNAME" )

这两个函数执行相同的操作 - 实际上。 但是,这里有一个关键的区别。 Fx1无法执行任何改变当前shell的操作。 这意味着修改变量,更改shell选项并声明其他功能。 后者可以被利用来防止名称间距问题,这些问题很容易在你身上蔓延。

# Fx1 cannot change the variable from a subshell
Fx0(){ Fx=0; }
Fx1()( Fx=1 )
Fx=foo; Fx0; echo $Fx
# 0
Fx=foo; Fx1; echo $Fx
# foo

话虽这么说,你应该使用“Fx0”类功能的唯一一次是当你想要重新声明当前shell中的某些内容时。 始终使用“Fx1”函数,因为它们更安全,您不必担心在其中声明的任何函数的命名。 正如您在下面所看到的,无辜函数被覆盖在Fx1中,但是,在执行Fx1之后它仍然没有受到损坏。

innocent_function()(
    echo ":)"
)
Fx1()(
    innocent_function()( true )
    innocent_function
)
Fx1 #prints nothing, just returns true
innocent_function
# :)

如果您使用花括号,这可能会产生(可能)意想不到的后果。 有用的“Fx0”类型函数的示例将专门用于更改当前shell,如下所示:

use_strict(){
    set -eEu -o pipefail
}
enable_debug(){
    set -Tx
}
disable_debug(){
    set +Tx
}

关于声明

使用全局变量,或者至少是那些预期有价值的变量,一直是不好的做法。 当您在bash中构建库时,您不希望函数依赖于已经设置的外部变量。 功能需要的任何东西都应该通过位置参数提供给它。 这是我在其他人试图用bash构建的库中看到的主要问题。 即使我发现了一些很酷的东西,我也无法使用它,因为我不知道我需要提前设置的变量的名称。 它导致挖掘所有代码,最终只是为自己挑选有用的部分。 到目前为止,为库创建的最佳函数非常小,甚至根本不使用命名变量。 以下面的例子为例:

serviceClient()(
    showUsage()(
        echo "This should be a help page"
    ) >&2
    isValidArg()(
        test "$(type -t "$1")" = "function"
    )
    isRunning()(
        nc -zw1 "$(getHostname)" "$(getPortNumber)"
    ) &>/dev/null
    getHostname()(
        echo localhost
    )
    getPortNumber()(
        echo 80
    )
    getStatus()(
        if isRunning
        then echo OK
        else echo DOWN
        fi
    )
    getErrorCount()(
        grep -c "ERROR" /var/log/apache2/error.log
    )
    printDetails()(
        echo "Service status: $(getStatus)"
        echo "Errors logged: $(getErrorCount)"
    )
    if isValidArg "$1"
    then "$1"
    else showUsage
    fi
)

通常情况下,您在顶部附近看到的是local hostname=localhostlocal port_number=80,这很好,但没有必要。 我认为,这些东西应该是功能化的,因为你正在构建以防止未来的痛苦,因为突然需要引入一些逻辑来获取值,例如:if isHttps; then echo 443; else echo 80; fi。 你不希望在你的主要功能中放置这种逻辑,否则你很快就会让它变得丑陋和无法管理。 现在,serviceClient具有在调用时声明的内部函数,这会为每次运行增加不明显的开销。 现在您可以使用service2Client,其功能(或外部函数)与serviceClient的名称相同,绝对没有冲突。 要记住的另一个重要事项是,重定向可以在声明它时应用于整个函数。请参阅:isRunning或showUsage 这与面向对象的关系非常接近,因为我认为你应该使用bash。

. serviceClient.sh
serviceClient
# This should be a help page
if serviceClient isRunning
then serviceClient printDetails
fi
# Service status: OK
# Errors logged: 0

我希望这可以帮助我的同伴抨击黑客。

答案 2 :(得分:9)

在函数内声明但没有local关键字的变量是全局变量。

最好只在需要local的函数内声明变量,以避免与其他函数和全局冲突(请参阅下面的foo())。

Bash函数库需要始终“来源”。我更喜欢使用'source'同义词而不是更常见的点(。),所以我可以在调试过程中更好地看到它。

以下技术至少适用于bash 3.00.16和4.1.5 ......

#!/bin/bash
#
# TECHNIQUES
#

source ./TECHNIQUES.source

echo
echo "Send user prompts inside a function to stderr..."
foo() {
    echo "  Function foo()..."              >&2 # send user prompts to stderr
    echo "    Echoing 'this is my data'..." >&2 # send user prompts to stderr
    echo "this is my data"                      # this will not be displayed yet
}
#
fnRESULT=$(foo)                       # prints: Function foo()...
echo "  foo() returned '$fnRESULT'"   # prints: foo() returned 'this is my data'

echo
echo "Passing global and local variables..."
#
GLOBALVAR="Reusing result of foo() which is '$fnRESULT'"
echo "  Outside function: GLOBALVAR=$GLOBALVAR"
#
function fn()
{
  local LOCALVAR="declared inside fn() with 'local' keyword is only visible in fn()"
  GLOBALinFN="declared inside fn() without 'local' keyword is visible globally"
  echo
  echo "  Inside function fn()..."
  echo "    GLOBALVAR=$GLOBALVAR"
  echo "    LOCALVAR=$LOCALVAR"
  echo "    GLOBALinFN=$GLOBALinFN"
}

# call fn()...
fn

# call fnX()...
fnX

echo
echo "  Outside function..."
echo "    GLOBALVAR=$GLOBALVAR"
echo
echo "    LOCALVAR=$LOCALVAR"
echo "    GLOBALinFN=$GLOBALinFN"
echo
echo "    LOCALVARx=$LOCALVARx"
echo "    GLOBALinFNx=$GLOBALinFNx"
echo

源代码函数库由...

表示
#!/bin/bash
#
# TECHNIQUES.source
#

function fnX()
{
  local LOCALVARx="declared inside fnX() with 'local' keyword is only visible in fnX()"
  GLOBALinFNx="declared inside fnX() without 'local' keyword is visible globally"
  echo
  echo "  Inside function fnX()..."
  echo "    GLOBALVAR=$GLOBALVAR"
  echo "    LOCALVARx=$LOCALVARx"
  echo "    GLOBALinFNx=$GLOBALinFNx"
}

运行TECHNIQUES会产生以下输出......

Send user prompts inside a function to stderr...
  Function foo()...
    Echoing 'this is my data'...
  foo() returned 'this is my data'

Passing global and local variables...
  Outside function: GLOBALVAR=Reusing result of foo() which is 'this is my data'

  Inside function fn()...
    GLOBALVAR=Reusing result of foo() which is 'this is my data'
    LOCALVAR=declared inside fn() with 'local' keyword is only visible in fn()
    GLOBALinFN=declared inside fn() without 'local' keyword is visible globally

  Inside function fnX()...
    GLOBALVAR=Reusing result of foo() which is 'this is my data'
    LOCALVARx=declared inside fnX() with 'local' keyword is only visible in fnX()
    GLOBALinFNx=declared inside fnX() without 'local' keyword is visible globally

  Outside function...
    GLOBALVAR=Reusing result of foo() which is 'this is my data'

    LOCALVAR=
    GLOBALinFN=declared inside fn() without 'local' keyword is visible globally

    LOCALVARx=
    GLOBALinFNx=declared inside fnX() without 'local' keyword is visible globally

答案 3 :(得分:7)

以下是我花了一个小时左右的谷歌搜索后发现的“值得你花时间” bash库的列表。

bashmenot是Halcyon和Haskell在Heroku上使用的一个库。上面的链接指向可用功能的完整列表,其中包含示例 - 令人印象深刻的质量,数量和文档。

MBFL提供了一组实现常用操作和脚本模板的模块。非常成熟的项目,仍然在github上活跃

您需要查看代码以获得简要说明和示例。它背后有几年的发展。

这是最基本的功能。对于文档,您还必须查看代码。

答案 4 :(得分:5)

我在这里发现了一篇很好但很旧的文章,它提供了一个完整的实用程序库列表:

http://dberkholz.com/2011/04/07/bash-shell-scripting-libraries/

答案 5 :(得分:2)

我可以告诉你,缺少可用的函数库与Bash的局限性无关,而是Bash的使用方式。 Bash是一种快速而肮脏的语言,用于自动化而非开发,因此对库的需求很少。然后,您开始在需要共享的函数和将函数转换为要调用的完整脚本之间找到一条细线。这是从编码的角度来看,由shell加载是另一回事,但通常是根据个人品味运行,不需要。所以...再次缺乏共享库。

以下是我经常使用的一些功能 在我的.bashrc

cd () {
   local pwd="${PWD}/"; # we need a slash at the end so we can check for it, too
   if [[ "$1" == "-e" ]]; then
      shift
      # start from the end
      [[ "$2" ]] && builtin cd "${pwd%/$1/*}/${2:-$1}/${pwd##*/$1/}" || builtin cd "$@"
   else
      # start from the beginning
      if [[ "$2" ]]; then
         builtin cd "${pwd/$1/$2}"
         pwd
      else
         builtin cd "$@"
      fi
   fi
}

一个版本的log()/ err()存在于函数库中,适用于编码器 - 主要是因为我们都使用相同的样式。

log() {
   echo -e "$(date +%m.%d_%H:%M) $@"| tee -a $OUTPUT_LOG
}

err() {
   echo -e "$(date +%m.%d_%H:%M) $@" |tee -a $OUTPUT_LOG
}

正如您所看到的,我们在这里使用的上述实用工具并不令人兴奋。我有另外一个库来制作关于bash限制的技巧,我认为这对他们来说是最好用的,我建议创建你自己的。