是否有任何常用(或不公正不常用)的 bash 函数实用程序“库”?类似于 Java 的 Apache commons-lang 之类的东西。Bash 无处不在,以至于在扩展库领域似乎被奇怪地忽视了。
6 回答
用于 bash 的库已经存在,但并不常见。bash 库稀缺的原因之一是由于功能的限制。我相信这些限制在“Greg's Bash Wiki”上得到了最好的解释:
职能。Bash 的“功能”有几个问题:
代码可重用性:Bash 函数不返回任何内容;它们只产生输出流。捕获该流并将其分配给变量或将其作为参数传递的每种合理方法都需要一个 SubShell,它会破坏对外部范围的所有分配。(另请参阅 BashFAQ/084 以了解从函数中检索结果的技巧。)因此,可重用函数库是不可行的,因为您不能要求函数将其结果存储在名称作为参数传递的变量中(除了通过执行评估后空翻)。
范围:Bash 有一个简单的本地范围系统,大致类似于“动态范围”(例如 Javascript、elisp)。函数可以看到调用者的本地变量(如 Python 的“nonlocal”关键字),但不能访问调用者的位置参数(如果启用了 extdebug,则通过 BASH_ARGV 除外)。除非您采用奇怪的命名规则来使冲突不太可能发生,否则无法保证可重用函数没有命名空间冲突。如果实现的函数期望作用于帧 n-3 中的变量名称,这可能已被您在 n-2 处的可重用函数覆盖,则这尤其是一个问题。Ksh93 可以通过使用“函数名 { ... }”语法声明函数来使用更常见的词法范围规则(Bash 不能,但无论如何都支持这种语法)。
闭包:在 Bash 中,函数本身总是全局的(具有“文件范围”),因此没有闭包。函数定义可能是嵌套的,但它们不是闭包,尽管它们看起来非常相似。函数不是“可通过的”(一流的),也没有匿名函数(lambdas)。事实上,没有什么是“可以通过的”,尤其是数组。Bash 使用严格的按值调用语义(魔术别名 hack 除外)。
还有更多复杂性涉及:子壳;导出函数;“功能崩溃”(定义或重新定义其他功能或自身的功能);陷阱(及其继承);以及函数与 stdio 交互的方式。不要因为不了解这一切而咬新人。Shell函数完全是f *** ed。
资料来源: http: //mywiki.wooledge.org/BashWeaknesses
shell“库”的一个示例是基于 Redhat 的系统上的 /etc/rc.d/functions。该文件包含 sysV 初始化脚本中常用的函数。
我在这里看到了一些好的信息和坏的信息。让我分享一下我所知道的,因为 bash 是我在工作中使用的主要语言(而且我们构建了库..)。总的来说,谷歌对 bash 脚本有一篇不错的文章,我认为这是一本不错的读物:https ://google.github.io/styleguide/shell.xml 。
首先让我说你不应该像使用其他语言的库那样考虑 bash 库。为了使 bash 中的库保持简单、有条理且最重要的是可重用,必须执行某些实践。
除了打印的字符串和函数的退出状态 (0-255) 之外,没有从 bash 函数返回任何内容的概念。这里有预期的限制和学习曲线,特别是如果您习惯于高级语言的功能。一开始可能会很奇怪,如果您发现自己处于字符串无法剪切的情况,您将需要利用外部工具,例如 jq。如果 jq (或类似的东西)可用,您可以开始让您的函数打印格式化输出,以便像对象、数组等一样进行解析和使用。
函数声明
在 bash 中声明函数有两种方法。一个在您当前的 shell 中运行,我们称之为 Fx0。并且产生一个子shell来操作,我们称之为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=localhost
,local port_number=80
这很好,但这不是必需的。我认为这些东西应该在构建时进行功能化,以防止将来突然出现一些逻辑来获取值时出现痛苦,例如:if isHttps; then echo 443; else echo 80; fi
. 您不希望将这种逻辑放置在您的 main 函数中,否则您很快就会使它变得丑陋和难以管理。现在, 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
我希望这可以帮助我的 bash 黑客伙伴。
这是我花了一个小时左右的谷歌搜索后发现的“值得你花时间”的bash 库列表。
bashmenot 是 Halcyon 和 Haskell 在 Heroku 上使用的库。上面的链接指向带有示例的可用功能的完整列表——令人印象深刻的质量、数量和文档。
MBFL 提供了一组实现常用操作的模块和一个脚本模板。相当成熟的项目,仍然活跃在 github
您需要查看代码以获取简要说明和示例。它背后有几年的发展。
这具有较少的最基本功能。对于文档,您还必须查看代码。
在函数内部声明但没有local
关键字的变量是全局变量。
最好只在函数内部声明变量,local
以避免与其他函数和全局冲突(参见下面的 foo())。
Bash 函数库需要始终是“来源”的。我更喜欢使用“源”同义词而不是更常见的点(。),这样我可以在调试过程中更好地看到它。
以下技术至少适用于 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
我在这里找到了一篇不错但很旧的文章,其中提供了实用程序库的完整列表:
http://dberkholz.com/2011/04/07/bash-shell-scripting-libraries/
我可以告诉你,缺乏可用的函数库与 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 限制的问题,我认为这是对它们的最佳用途,我建议您创建自己的库。