当前位置: 首页 > 科技观察

你不知道的Bash:Bash数组简介

时间:2023-03-15 11:20:58 科技观察

进入Bash数组的奇异而神奇的世界。虽然软件工程师经常使用命令行进行各种开发,但是命令行上的数组似乎总是一个晦涩难懂的东西(虽然不像常规运算符那么复杂晦涩=~)。尽管语法晦涩难懂,但Bash数组实际上非常有用。等等,这是为什么呢?很难写一些关于Bash的东西,但是写一篇类似手册的文章却很容易,它专注于奇怪的语法。但别担心,本文的目的是让您免于阅读该死的手册。真实的(通常有用的)示例为此,想象一个真实的场景以及Bash如何提供帮助:您正在公司领导一项新工作,评估和优化内部数据管道的运行时间。首先,您进行参数扫描分析以评估管道使用线程的情况。为简单起见,我们将管道视为编译后的C++黑盒,我们可以调整的唯一参数是用于处理数据的线程数:./pipeline--threads4。基础我们需要做的第一件事是定义一个数组来容纳我们要测试的--threads参数:allThreads=(1248163264128)在这个例子中,所有元素都是数字,但是参数是不一定是数字,Bash中的数组可以容纳数字和字符串,例如myArray=(12"three"4"five")是一个有效的表达式。就像Bash中的任何其他变量一样,确保赋值符号周围没有空格。否则Bash会将变量名作为程序执行,使用=作为程序的第一个参数。现在我们初始化了数组,让我们解析它的一些元素。只需键入echo$allThreads,您可以看到它只输出第一个元素。要理解其中的原因,我们需要回到上一步,回顾一下我们通常如何在Bash中输出变量。考虑以下场景:type="article"echo"Found42$type"假设我们得到的变量$type是一个单词,我们想在句子的末尾添加一个s。我们不能只将s添加到$type,因为那样会将它变成另一个变量$types。虽然我们可以利用像echo"Found42"$type"s"这样的代码变形,但解决这个问题的唯一方法是使用一个花括号:echo"Found42${type}s",它允许我们告诉Bash变量名开始和结束的地方(有趣的是,JavaScript/ES6在模板字面量中注入变量和表达式的语法与此处相同)。事实上,虽然Bash变量一般不用花括号,但在数组中需要用花括号。这反而允许我们指定要访问的索引,例如echo${allThreads[1]}返回数组中的第二个元素。如果不写花括号,比如echo$allThreads[1],会导致Bash把[1]当成字符串输出。是的,Bash数组有奇怪的语法,但至少它们是零索引的,不像某些语言(我说的是你,R)。遍历数组在上面的例子中,我们直接使用整数作为数组的索引。现在我们考虑另外两种情况:***,如果你想要数组中的第$i个元素,其中$i是一个代表索引的变量,我们可以通过echo${allThreads[$i]}来解析这个元素像这样。其次,要输出数组的所有元素,我们用@符号替换数字索引(您可以使用@作为所有的符号):echo${allThreads[@]}。遍历数组元素还记得我们上面说的吗,我们遍历$allThreads数组,将每个值作为--threads参数来启动管道:fortin${allThreads[@]};do./pipeline--threads$tdonetraverseArrayIndexing接下来,考虑一种稍微不同的方法。我们可以遍历所有索引,而不是遍历所有数组元素:foriin${!allThreads[@]};do./pipeline--threads${allThreads[$i]}一步步完成:如前所述,${allThreads[@]}表示数组中的所有元素。前面有一个感叹号,它变成${!allThreads[@]},它返回一个数组索引列表(这里是0到7)。换句话说。for循环遍历所有索引$i并从$allThreads中读取第$i个元素作为--threads选项的参数。这看起来很刺激,您可能想知道为什么我首先要谈论它。这是因为有时候在循环中需要同时获取索引和对应的值,比如想忽略数组中的第一个元素,使用索引可以避免创建额外的变量,在循环中累加.填充数组到目前为止,我们已经能够使用给定的--threads选项启动管道。现在假设以秒为单位的经过时间输出到管道。我们想捕获每次迭代的输出并将其存储在另一个数组中,以便我们最终可以随心所欲地操作它。一些有用的语法在深入研究代码之前,我们将介绍更多语法。首先,我们需要能够解析Bash命令的输出。这可以通过以下语法完成:output=$(./my_script.sh),它将命令的输出存储到变量$output中。我们需要的第二个语法是如何将我们刚刚解析的值添加到数组中。这个任务的语法看起来很熟悉:myArray+=("newElement1""newElement2")参数扫描一切就绪,执行参数扫描的步骤如下:allThreads=(1248163264128)allRuntimes=()fortin${allThreads[@]};doruntime=$(./pipeline--threads$t)allRuntimes+=($runtime)done就是这样!还能做什么?在这篇文章中,我们谈到了使用数组进行参数扫描的场景。我确信使用Bash数组的原因有很多,这里有两个例子:记录警告在这种情况下,应用程序被分成几个模块,每个模块都有自己的日志文件。我们可以编写一个cron任务脚本,当模块中出现问题标志时向特定人员发送电子邮件:#日志列表,出现问题时应该通知谁logPaths=("api.log""auth.log""jenkins.log""data.log")logEmails=("jay@email""emma@email""jon@email""sophia@email")#在${!logPaths[@]中为i在每个日志中查找问题标志};dolog=${logPaths[$i]}stakeholder=${logEmails[$i]}numErrors=$(tail-n100"$log"|grep"ERROR"|wc-l)#如果最近如果更多发现超过5个错误,如果[["$numErrors"-gt5]]警告负责人;然后emailRecipient="$stakeholder"emailSubject="警告:${log}显示异常级别的错误"emailBody="${numErrors}在日志${log}中发现错误"echo"$emailBody"|mailx-s"$emailSubject""$emailRecipient"fidoneAPIquery如果您想生成一些分析数据,请分析您的Medium帖子中评论最多的用户。由于我们无法直接访问数据库,SQL不在我们的范围内,但我们可以使用API!为避免陷入有关API授权和令牌的冗长讨论,我们将使用JSONPlaceholder,这是一种面向公众的测试服务API。一旦我们查询每个帖子并解析出每个评论者的电子邮件,我们就可以将这些电子邮件添加到我们的结果数组中:endpoint="https://jsonplaceholder.typicode.com/comments"allEmails=()#Querythefirst10postsfor{1..10}中的postId;do#执行API调用以获取帖子评论者的电子邮件地址response=$(curl"${endpoint}?postId=${postId}")#使用jq将JSON响应解析为数组allEmails+=($(jq'.[].email'<<<"$response"))done注意这里我使用jq工具从命令行解析JSON数据。jq的语法超出了本文的范围,但我强烈建议您了解一下。正如您可能想象的那样,使用Bash数组可以在无数场景中发挥作用,我希望本文中的示例能够激发您的思考。如果您从自己的作品中找到其他想要分享的示例,请在帖子下方发表评论。请稍等,还有很多!由于我们在这篇文章中讲了很多数组语法,这里总结一下我们讲过的内容,包括一些还没有讲到的高级技巧:<如果显示不完整,请左右滑动>语法effectarr=()创建一个空数组arr=(123)初始化数组${arr[2]}获取第三个元素${arr[@]}获取所有元素${!arr[@]}获取数组索引${#arr[@]}计算数组长度arr[0]=3覆盖第一个元素arr+=(4)添加值str=$(ls)保存ls输出到字符串arr=($(ls))保存ls输出文件toarray${arr[@]:s:n}获取从索引s开始的n个元素***一点想法正如我们所见,Bash数组语法很奇怪,但我希望这篇文章能让你相信它们是有用的。理解了语法后,您会发现自己以后会经常使用Bash数组。Bash还是Python?问题来了:我什么时候应该使用Bash数组而不是其他脚本语法,如Python?对我来说,这完全取决于需求——如果你可以调用一个命令行工具并开箱即用地解决问题,你也可以使用Bash。但有时,当您的脚本是更大的Python项目的一部分时,您也可以使用Python。例如,我们可以使用Python实现参数扫描,但我们只需要写一个Bash包装器:importsubprocessall_threads=[1,2,4,8,16,32,64,128]all_runtimes=[]#usedifferentthreadsDigitalstartuppipelinefortinall_threads:cmd='./pipeline--threads{}'.format(t)#使用子进程模块获取返回的输出p=subprocess.Popen(cmd,stdout=subprocess.PIPE,shell=True)output=p.communicate()[0]all_runtimes.append(output)由于本例绕不开命令行,可以先使用Bash。