Bash实用程序脚本库

Ram*_*mon 54 bash

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

jor*_*anm 38

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语义(魔术别名hack除外).

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

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

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

  • 我想我一方面可以计算出我投下的数量,但是慷慨接受这个答案迫使我在这里做出例外.作为一名退休的Unix和Linux程序员以及30多年的DBA,我对这个答案不能不同意.与大多数shell一样,Bash是一个更高级别(更抽象的),在解释器中实现C语言.像C一样,Bash使用平坦的函数范围.它与OOL不同,但不是限制性的.当可变范围尽可能保持在本地时,代码可重用性和返回值功能非常出色. (11认同)
  • 虽然这是很棒的信息,但它根本没有回答这个问题 (7认同)
  • 这个答案显然是为什么Bash不用于开发项目(而非其意图)的答案,而不是为什么它没有共享函数库.我目前使用函数库作为我公司的高级自动化工程师.从一切到控制分叉进程,再到文件传输到云.上面有很多我不同意我的个人和专业经验. (6认同)
  • 这个答案很有说服力.可以将函数视为在集中文件中定义的命令脚本,这非常有用.所有的要点都很容易解决:即$(namespaced_func).可能大多数语言不鼓励按值分配变量.还有避免功能性副作用的范例.此外,您可以分配全局变量,以实现所有密集目的的值分配.我认为删除这个答案并专注于评估函数库会很有效 (5认同)
  • 我不会说"根本".第一句直接回答了问题,虽然它含糊不清. (4认同)
  • 请注意,包含*javascript*作为动态范围语言的示例是*错误的*; javascript确实有一个特定的区域,它提供了"动态"功能 - "this"机制(以及一些其他模糊和特定的区域,例如`with`),但除此之外,它是**词法范围**全面. (2认同)
  • -1"代码可重用性:Bash函数不返回任何内容"你的第一要点就是它,我很抱歉说错了.函数返回操作,状态和文件.一旦了解了如何使用这些功能,就可以编写丰富的函数库. (2认同)

use*_*dly 17

我在这里看到一些好消息和不良信息.让我分享我所知道的,因为bash是我在工作中使用的主要语言(我们构建库......).谷歌在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" )
Run Code Online (Sandbox Code Playgroud)

这两个函数执行相同的操作 - 实际上.但是,这里有一个关键的区别.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
Run Code Online (Sandbox Code Playgroud)

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

innocent_function()(
    echo ":)"
)
Fx1()(
    innocent_function()( true )
    innocent_function
)
Fx1 #prints nothing, just returns true
innocent_function
# :)
Run Code Online (Sandbox Code Playgroud)

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

use_strict(){
    set -eEu -o pipefail
}
enable_debug(){
    set -Tx
}
disable_debug(){
    set +Tx
}
Run Code Online (Sandbox Code Playgroud)

关于声明

全局变量的使用,或者至少是那些预期有价值的变量,都是不好的做法.当您在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
)
Run Code Online (Sandbox Code Playgroud)

通常情况下,你会看到顶部附近的东西local hostname=localhost,local 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
Run Code Online (Sandbox Code Playgroud)

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

  • 为什么我不能多次对该评论点赞?关于像命名空间一样使用子 shell 函数的绝妙想法。我喜欢使用子 shell 函数的另外两个好处。1)您不必对变量使用“local”,这样可以减少输入量。2) 通过不使用“local”,分配给变量时的命令替换会保留退出/返回状态。呃。当你执行 `local x=$(foo)` 时,`$?` 将*始终*为 0,即使 `foo` 失败。而“$?”在执行简单的“x=$(foo)”时不会遇到这个问题 (3认同)
  • 感谢您的澄清。你说服了我。 (2认同)

Doc*_*ger 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
Run Code Online (Sandbox Code Playgroud)

源代码函数库由......表示

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

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

  • 使用`cat << - EOF`代替echo,echo,echo,echo ... (3认同)
  • @tajagi - 我个人一直使用heredoc,但是......任何有价值的编辑器都能够回显 - 如果你包含了编辑器功能,那么heredoc和echo之间的区别归结为单纯的语法 - a领带由个人喜好决定.Doc对他的偏好给出了完整的解释. (2认同)
  • "...每个有价值的编辑器都可以配置为遵循您的编码标准.**暗示您首先拥有任何...**Heredoc可以与您的代码对齐使用,**如果你打开一个人bash十年.**"----你的话.粗体部分是你的无偿推论,与任何论点无关.我相信如果你找他们有堆栈交换的指导方针,基本上说避免无偿的减少.在这种情况下,你甚至没有解决OP的问题,只是DocSalvager使用了4行echo而不是heredoc这一事实.寒冷和重构.:) (2认同)

nde*_*mou 9

这是我花了一个小时左右的谷歌搜索后发现的"值得你花时间" bash库的列表.

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

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

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

这具有最少的基本功能.对于文档,您还必须查看代码.


小智 6

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

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