Bash 实用程序脚本库

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11369522/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 22:23:16  来源:igfitidea点击:

Bash utility script library

bash

提问by Ramon

Is there any commonly used (or unjustly uncommonly used) utility "library" of bash functions? Something like Apache commons-lang for Java. Bash is so ubiquitous that it seems oddly neglected in the area of extension libraries.

是否有任何常用(或不公正地不常用)的 bash 函数实用程序“库”?类似于 Java 的 Apache commons-lang。Bash 无处不在,以至于它在扩展库领域似乎很奇怪地被忽视了。

采纳答案by jordanm

Libraries for bash are out there, but not common. One of the reasons that bash libraries are scarce is due to the limitation of functions. I believe these limitations are best explained on "Greg's Bash Wiki":

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

Functions. Bash's "functions" have several issues:

职能。Bash 的“函数”有几个问题:

  • Code reusability: Bash functions don't return anything; they only produce output streams. Every reasonable method of capturing that stream and either assigning it to a variable or passing it as an argument requires a SubShell, which breaks all assignments to outer scopes. (See also BashFAQ/084 for tricks to retrieve results from a function.) Thus, libraries of reusable functions are not feasible, as you can't ask a function to store its results in a variable whose name is passed as an argument (except by performing eval backflips).

  • Scope: Bash has a simple system of local scope which roughly resembles "dynamic scope" (e.g. Javascript, elisp). Functions see the locals of their callers (like Python's "nonlocal" keyword), but can't access a caller's positional parameters (except through BASH_ARGV if extdebug is enabled). Reusable functions can't be guaranteed free of namespace collisions unless you resort to weird naming rules to make conflicts sufficiently unlikely. This is particularly a problem if implementing functions that expect to be acting upon variable names from frame n-3 which may have been overwritten by your reusable function at n-2. Ksh93 can use the more common lexical scope rules by declaring functions with the "function name { ... }" syntax (Bash can't, but supports this syntax anyway).

  • Closures: In Bash, functions themselves are always global (have "file scope"), so no closures. Function definitions may be nested, but these are not closures, though they look very much the same. Functions are not "passable" (first-class), and there are no anonymous functions (lambdas). In fact, nothing is "passable", especially not arrays. Bash uses strictly call-by-value semantics (magic alias hack excepted).

  • There are many more complications involving: subshells; exported functions; "function collapsing" (functions that define or redefine other functions or themselves); traps (and their inheritance); and the way functions interact with stdio. Don't bite the newbie for not understanding all this. Shell functions are totally f***ed.

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

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

  • 闭包:在 Bash 中,函数本身始终是全局的(具有“文件范围”),因此没有闭包。函数定义可能是嵌套的,但它们不是闭包,尽管它们看起来非常相似。函数不是“可传递的”(一流的),并且没有匿名函数(lambdas)。事实上,没有什么是“可通过的”,尤其是数组。Bash 使用严格的按值调用语义(魔术别名黑客除外)。

  • 还有更多的复杂性涉及:子壳;导出函数;“函数折叠”(定义或重新定义其他函数或自身的函数);陷阱(及其继承);以及函数与 stdio 交互的方式。不要因为不了解这一切而咬新手。Shell 函数完全是他妈的。

Source: http://mywiki.wooledge.org/BashWeaknesses

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

One example of a shell "library" is /etc/rc.d/functions on Redhat based system. This file contains functions commonly used in sysV init script.

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

回答by user.friendly

I see some good info and bad info here. Let me share what I know since bash is the primary language I use at work (and we build libraries..). Google has a decent write up on bash scripts in general that I thought was a good read: https://google.github.io/styleguide/shell.xml.

我在这里看到一些好的信息和坏的信息。让我分享我所知道的,因为 bash 是我在工作中使用的主要语言(并且我们构建了库......)。谷歌在 bash 脚本上有一篇不错的文章,我认为这是一个很好的阅读:https: //google.github.io/styleguide/shell.xml

Let me start by saying you should not think of a bash library as you do libraries in other languages. There are certain practices that must be enforced to keep a library in bash simple, organized, and most importantly, reusable.

首先让我说您不应该像使用其他语言的库那样考虑 bash 库。必须强制执行某些实践,以保持 bash 中的库简单、有条理,最重要的是,可重用。

There is no concept of returning anything from a bash function except for strings that it prints and the function's exit status (0-255). There are expected limitations here and a learning curve especially if you're accustomed to functions of higher-level languages. It can be weird at first, and if you find yourself in a situation where strings just aren't cutting it, you'll want to leverage an external tool such as jq. If jq (or something like it) is available, you can start having your functions print formatted output to be parsed & utilized as you would an object, array, etc.

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

Function Declarations

函数声明

There are two ways to declare a function in bash. One operates within your current shell, we'll call is Fx0. And one spawns a subshell to operate in, we'll call that Fx1. Here are examples of how they're declared:

在 bash 中有两种方法可以声明一个函数。一个在您当前的 shell 中运行,我们称之为 Fx0。并且生成一个子shell 来操作,我们将其称为Fx1。以下是如何声明它们的示例:

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

These 2 functions perform the same operation - indeed. However, there is a key difference here. Fx1 cannot perform any action that alters the current shell. That means modifying variables, changing shell options and declaring other functions. The latter is what can be exploited to prevent name spacing issues that can easily creep up on you.

这两个函数执行相同的操作 - 实际上。然而,这里有一个关键的区别。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

That being said, The only time you should use an "Fx0" kind of function is when you're wanting to redeclare something in the current shell. Always use "Fx1" functions because they are safer and you you don't have to worry about the naming of any functions declared within it. As you can see below, the innocent function is overwritten inside of Fx1, however, it remains unscathed after the execution of Fx1.

话虽如此,您应该使用“Fx0”类型的函数的唯一时间是当您想要在当前 shell 中重新声明某些内容时。始终使用“Fx1”函数,因为它们更安全,而且您不必担心在其中声明的任何函数的命名。正如您在下面看到的,无辜的函数在 Fx1 内部被覆盖,但是,在执行 Fx1 后它仍然毫发无损。

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

This would have (likely) unintended consequences if you had used curly braces. Examples of useful "Fx0" type functions would be specifically for changing the current shell, like so:

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

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

Regarding Declarations

关于声明

The use of global variables, or at least those expected to have a value, is bad practice all the way around. As you're building a library in bash, you don't ever want a function to rely on an external variable already being set. Anything the function needs should be supplied to it via the positional parameters. This is the main problem I see in libraries other folks try to build in bash. Even if I find something cool, I can't use it because I don't know the names of the variables I need to have set ahead of time. It leads to digging through all of the code and ultimately just picking out the useful pieces for myself. By far, the best functions to create for a library are extremely small and don't utilize named variables at all, even locally. Take the following for example:

The use of global variables, or at least those expected to have a value, is bad practice all the way around. As you're building a library in bash, you don't ever want a function to rely on an external variable already being set. Anything the function needs should be supplied to it via the positional parameters. This is the main problem I see in libraries other folks try to build in bash. Even if I find something cool, I can't use it because I don't know the names of the variables I need to have set ahead of time. It leads to digging through all of the code and ultimately just picking out the useful pieces for myself. By far, the best functions to create for a library are extremely small and don't utilize named variables at all, even locally. Take the following for example:

serviceClient()(
    showUsage()(
        echo "This should be a help page"
    ) >&2
    isValidArg()(
        test "$(type -t "")" = "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 ""
    then ""
    else showUsage
    fi
)

Typically, what you would see near the top is local hostname=localhostand local port_number=80which is fine, but it is not necessary. It is my opinion that these things should be functional-ized as you're building to prevent future pain when all of a sudden some logic needs to be introduced for getting a value, like: if isHttps; then echo 443; else echo 80; fi. You don't want that kind of logic placed in your main function or else you'll quickly make it ugly and unmanageable. Now, serviceClient has internal functions that get declared upon invocation which adds an unnoticeable amount of overhead to each run. The benefit is now you can have service2Client with functions (or external functions) that are named the same as what serviceClient has with absolutely no conflicts. Another important thing to keep in mind is that redirections can be applied to an entire function upon declaring it. see: isRunning or showUsage This gets as close to object-oriented-ness as I think you should bother using bash.

通常,您会在顶部附近看到的是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

I hope this helps my fellow bash hackers out there.

我希望这可以帮助我的 bash 黑客伙伴。

回答by DocSalvager

Variables declared inside a function but without the localkeyword are global.

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

It's good practice to declare variables only needed inside a function with localto avoid conflicts with other functions and globally (see foo() below).

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

Bash function libraries need to always be 'sourced'. I prefer using the 'source' synonym instead of the more common dot(.) so I can see it better during debugging.

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

The following technique works in at least bash 3.00.16 and 4.1.5...

以下技术至少适用于 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

The sourced function library is represented by...

源函数库表示为...

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

Running TECHNIQUES produces the following output...

运行 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

回答by ndemou

Here's a list of "worthy of your time"bash libraries that I found after spending an hour or so googling.

这是我花了一个小时左右的谷歌搜索后找到的“值得你花时间”的bash 库列表。

bashmenot is a library that is used by Halcyon and Haskell on Heroku. The above link points to a complete list of available functions with examples -- impressive quality, quantity and documentation.

bashmenot 是 Halcyon 和 Haskell 在 Heroku 上使用的库。上面的链接指向可用功能的完整列表和示例——令人印象深刻的质量、数量和文档。

MBFL offers a set of modules implementing common operations and a script template. Pretty mature project and still active on github

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

You need to look at the code for a brief description and examples. It has a few years of development in its back.

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

This has the fewer most basic functions. For documentation you also have to look at the code.

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

回答by Chris Novak

I found a good but old article here that gave a comprehensive list of utility libraries:

我在这里找到了一篇很好但很旧的文章,其中提供了实用程序库的完整列表:

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

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

回答by SaintHax

I can tell you that the lack of available function libraries has nothing to do with Bash's limitations, but rather how Bash is used. Bash is a quick and dirty language made for automation, not development, so the need for a library is rare. Then, you start to have a fine line between a function that needs to be shared, and converting the function into a full fledged script to be called. This is from a coding perspective, to be loaded by a shell is another matter, but normally runs on personal taste, not need. So... again a lack of shared libraries.

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

Here are a few functions I use regularly In my .bashrc

以下是我经常使用的一些函数 在我的 .bashrc 中

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

And a version of a log()/err() exists in a function library at work for coders-- mainly so we all use the same style.

一个版本的 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
}

As you can see, the above utilities we use here, are not that exciting to share. I have another library to do tricks around bash limitations, which I think is the best use for them and I recommend creating your own.

如您所见,我们在这里使用的上述实用程序并没有那么令人兴奋。我有另一个库可以解决 bash 限制的问题,我认为这是它们的最佳用途,我建议您创建自己的库。