Bash实用程序脚本库

是否有任何常用的(或不正当使用)bash函数的实用程序“库”? 就像Apache commons-lang for Java一样。 Bash是如此无处不在,在扩展库方面似乎被忽略了。

狂欢的图书馆在那里,但不常见。 bash库稀缺的原因之一是由于function的限制。 我相信这些限制是最好的解释“格雷格的Bash维基”:

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

  • 代码可重用性 :Bash函数不返回任何东西; 他们只产生输出stream。 捕获该stream并将其分配给variables或将其作为parameter passing的每个合理方法都需要一个SubShell,它将所有赋值分配给外部作用域。 (也可以参考BashFAQ / 084中关于从函数中检索结果的技巧)。因此,可重用函数库是不可行的,因为您不能要求函数将结果存储在名称作为parameter passing的variables中通过执行eval backflips)。

  • 范围 :Bash有一个简单的局部范围系统,大致类似于“dynamic范围”(如Javascript,elisp)。 函数看到它们的调用者的地方(像Python的“nonlocal”关键字),但不能访问调用者的位置参数(除非通过BASH_ARGV,如果启用了extdebug)。 除非您使用奇怪的命名规则来使冲突发生的可能性不足,否则无法保证可重用函数不受命名空间冲突的影响。 如果实现的function期望作用于第n-3帧的variables名,而这个variables名可能已被n-2中的可重用函数覆盖,那么这尤其是个问题。 Ksh93可以通过使用“函数名{…}”语法(Bash不能,但支持这种语法)来使用更常见的词法作用域规则。

  • 闭包 :在Bash中,函数本身总是全局的(具有“文件范围”),所以没有closures。 函数定义可能是嵌套的,但这些并不是闭包,虽然看起来非常相似。 函数不是“可通过的”(first-class),并且没有匿名函数(lambdas)。 事实上,没有什么是“可通过的”,特别是不是数组。 Bash严格使用按值调用的语义(除了魔法别名黑客)。

  • 还有更多的并发症:子壳体; 导出函数; “function崩溃”(定义或重新定义其他function或自己的function); 陷阱(及其inheritance); 和函数与stdio交互的方式。 不要因为不理解这一切而咬新手。 Shell函数完全可以使用。

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

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

在函数中声明但没有local关键字的variables是全局variables。

声明local函数中需要的variables是很好的做法,以避免与其他函数和全局冲突(参见下面的foo())。

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

以下技术至less在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 

我在这里find了一篇很好但很老的文章,给出了一个实用程序库的完整清单:

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

我在这里看到一些很好的信息和不好的信息。 让我分享我所知道的,因为bash是我在工作中使用的主要语言(我们build立图书馆..)。 谷歌有一个体面的写在一般的bash脚本,我认为是一个很好的阅读: https : //google.github.io/styleguide/shell.xml 。

让我首先说你不应该想象一个bash库,因为你用其他语言的库。 有一些必须强制执行的做法,使图书馆简单,有组织,最重要的是,可重复使用。

除了打印的string和函数的退出状态(0-255),没有任何东西从bash函数返回。 这里有一些预期的限制和一个学习曲线,特别是如果你习惯于更高级的语言的function。 起初它可能很奇怪,如果你发现自己处于一个string没有被切断的情况,你会想要利用诸如jq之类的外部工具。 如果jq(或类似的东西)可用,你可以开始让你的函数打印格式化的输出被parsing和利用,就像你将一个对象,数组等。

函数声明

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

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

这两个function执行相同的操作 – 的确如此。 但是,这里有一个关键的区别。 Fx1不能执行任何改变当前shell的动作。 这意味着修改variables,更改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”types的function是当你要重新声明当前shell中的东西。 总是使用“Fx1”function,因为它们更安全,您不必担心在其中声明的任何函数的命名。 正如您在下面看到的那样,无辜的函数被覆盖在Fx1内部,但是在执行完Fx1之后它仍然没有受到攻击。

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

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

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

关于声明

全局variables的使用,或者至less是那些预期会有价值的variables,一直是不好的做法。 当你在bash中build立一个库时,你永远不希望一个函数依赖于已经设置的外部variables。 应该通过位置参数提供该函数所需的任何东西。 这是我在图书馆看到的其他人尝试在bash中build立的主要问题。 即使我发现一些很酷的东西,我也无法使用它,因为我不知道需要提前设置的variables的名称。 它导致通过所有的代码挖掘,最终只是为自己挑选有用的作品。 到目前为止,为图书馆创build的最佳function是非常小的,甚至在本地也不使用命名variables。 以下面的例子为例:

 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这是好的,但没有必要。 我认为,当你为了防止未来的痛苦时,这些事情应该被function化,当突然之间需要引入一些逻辑来获得价值时,比如: if isHttps; then echo 443; else echo 80; fi if isHttps; then echo 443; else echo 80; fi if isHttps; then echo 443; else echo 80; fi 。 你不希望把这种逻辑放在你的主要function,否则你会很快变得丑陋和难以pipe理。 现在,serviceClient具有内部函数,这些内部函数在调用时被声明,从而为每次运行添加不明显的开销。 好处是你可以有service2Client的function(或外部函数),它们被命名为与serviceClient完全没有冲突的命名相同。 要记住的另一件重要的事情是redirect可以在声明它时应用于整个函数。 请参阅: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 

我希望这可以帮助我的同伴黑客在那里。

我可以告诉你,缺less可用的函数库与Bash的限制无关,而是Bash是如何使用的。 Bash是一种快速而肮脏的语言,是为了自动化,而不是开发,所以对图书馆的需求是很less见的。 然后,开始在需要共享的函数和将函数转换为完整的脚本之间有一条细线。 这是从编码的angular度来看,被一个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限制做技巧,我认为这对他们是最好的,我build议创build自己的。