尽管软件工程师经常在开发的许多方面都使用命令行,但是数组可能是命令行中比较模糊的功能之一(尽管不如regex运算符=~
那么模糊)。 但是除了晦涩难懂的语法外, Bash数组可能非常强大。
等等,为什么呢?
关于Bash的写作具有挑战性,因为将一篇文章转为侧重于语法奇数的手册非常容易。 不过,请放心,本文的目的是避免让您成为RTFM。
一个真实的(实际有用的)例子
为此,让我们考虑一个真实的场景以及Bash如何提供帮助:您正在为公司做出新的努力,以评估和优化内部数据管道的运行时间。 第一步,您要进行参数扫描以评估管道对线程的利用程度。 为了简单起见,我们将管道视为已编译的C ++黑匣子,其中唯一可以调整的参数是为数据处理保留的线程数: ./pipeline --threads 4
。
基础
我们要测试的--threads
参数:
allThreads = ( 1 2 4 8 16 32 64 128 )
在此示例中,所有元素都是数字,但并非必须如此-Bash中的数组可以包含数字和字符串,例如, myArray=(1 2 "three" 4 "five")
是有效表达式。 与其他Bash变量一样,请确保在等号周围不留空格。 否则,Bash会将变量名称视为要执行的程序,并将=
作为其第一个参数!
现在我们已经初始化了数组,让我们检索它的一些元素。 您会注意到,仅执行echo $allThreads
只会输出第一个元素。
要了解为什么会发生这种情况,让我们退后一步,回顾一下我们通常在Bash中输出变量的方式。 请考虑以下情形:
type =
"article"
echo
"Found 42 $type "
假设变量$type
以单数名词的$type
提供给我们,我们想在句子的末尾添加s
。 我们不能简单地在$type
添加s
,因为那样会将其变成另一个变量$types
。 而且,尽管我们可以利用诸如echo "Found 42 "$type"s"
代码扭曲,但是解决此问题的最佳方法是使用花括号: echo "Found 42 ${type}s"
,这使我们能够告诉Bash变量名称的开始和结束位置(有趣的是,这与JavaScript / ES6中用于在模板文字中注入变量和表达式的语法相同)。
事实证明,尽管Bash变量通常不需要大括号,但数组需要它们。 反过来,这允许我们指定要访问的索引,例如echo ${allThreads[1]}
返回数组的第二个元素。 不包括方括号,例如echo $allThreads[1]
,会导致Bash将[1]
视为字符串并echo $allThreads[1]
输出。
是的,Bash数组的语法很奇怪,但至少与其他某些语言不同(我在看着您R
),它们是零索引的。
遍历数组
尽管在上面的示例中,我们在数组中使用了整数索引,但让我们考虑以下两种情况:首先,如果我们想要数组的$i
-th元素,其中$i
是包含索引的变量如果感兴趣,我们可以使用以下方法检索该元素: echo ${allThreads[$i]}
。 其次,要输出数组的所有元素,我们将数字索引替换为@
符号(您可以将@
代表all
): echo ${allThreads[@]}
。
遍历数组元素
考虑到这一点,让我们遍历$allThreads
和发射的每个值管道--threads
:
for t
in
${allThreads[@]} ;
do
.
/ pipeline
--threads
$t
done
遍历数组索引
接下来,让我们考虑一种稍微不同的方法。 除了遍历数组元素 ,我们还可以遍历数组索引 :
for i
in
${!allThreads[@]} ;
do
.
/ pipeline
--threads
${allThreads[$i]}
done
让我们分解一下:正如我们在上面看到的, ${allThreads[@]}
代表数组中的所有元素。 添加一个感叹号使其成为${!allThreads[@]}
将返回所有数组索引的列表(在我们的示例中为0到7)。 换句话说, for
循环遍历所有索引$i
并从$allThreads
读取$i
-th个元素以设置--threads
参数的值。
这在眼睛上要严厉得多,因此您可能想知道为什么我一开始就引入它。 那是因为有时候您需要同时知道索引和循环中的值,例如,如果您想忽略数组的第一个元素,则使用索引可以避免创建额外的变量,然后在循环内递增。
填充数组
到目前为止,我们已经能够为每个感兴趣的--threads
启动管道。 现在,假设流水线的输出是以秒为单位的运行时间。 我们希望在每次迭代时捕获该输出,并将其保存在另一个数组中,以便最后可以对它进行各种操作。
一些有用的语法
但是在深入研究代码之前,我们需要引入更多语法。 首先,我们需要能够检索Bash命令的输出。 为此,请使用以下语法: output=$( ./my_script.sh )
,它将命令的输出存储到变量$output
。
我们需要的语法的第二位是如何将刚检索到的值附加到数组中。 这样做的语法看起来很熟悉:
myArray+= ( "newElement1" "newElement2" )
参数扫描
放在一起,这是启动参数扫描的脚本:
allThreads =
(
1
2
4
8
16
32
64
128
)
allRuntimes =
(
)
for t
in
${allThreads[@]} ;
do
runtime =$
( .
/ pipeline
--threads
$t
)
allRuntimes+=
(
$runtime
)
done
和瞧!
你还有什么?
在本文中,我们介绍了使用数组进行参数扫描的方案。 但我保证还有更多使用Bash数组的理由-这里还有另外两个示例。
日志警报
在这种情况下,您的应用程序分为多个模块,每个模块都有自己的日志文件。 当某些模块出现故障迹象时,我们可以编写cron作业脚本以向合适的人发送电子邮件:
# List of logs and who should be notified of issues
logPaths =
(
"api.log"
"auth.log"
"jenkins.log"
"data.log"
)
logEmails =
(
"jay@email"
"emma@email"
"jon@email"
"sophia@email"
)
# Look for signs of trouble in each log
for i
in
${!logPaths[@]} ;
do
log =
${logPaths[$i]}
stakeholder =
${logEmails[$i]}
numErrors =$
(
tail
-n
100
" $log "
|
grep
"ERROR"
|
wc
-l
)
# Warn stakeholders if recently saw > 5 errors
if
[
[
" $numErrors "
-gt
5
]
] ;
then
emailRecipient =
" $stakeholder "
emailSubject =
"WARNING: ${log} showing unusual levels of errors"
emailBody =
" ${numErrors} errors found in log ${log} "
echo
" $emailBody "
| mailx
-s
" $emailSubject "
" $emailRecipient "
fi
done
API查询
假设您要生成一些分析,以了解哪些用户在您的“中型”帖子中评论最多。 由于我们没有直接的数据库访问权限,因此SQL是不可能的,但是我们可以使用API!
为了避免深入讨论API身份验证和令牌,我们将使用JSONPlaceholder (一个面向公众的API测试服务)作为我们的端点。 一旦查询了每个帖子并检索了发表评论的每个人的电子邮件,就可以将这些电子邮件追加到结果数组中:
endpoint =
"https://jsonplaceholder.typicode.com/comments"
allEmails =
(
)
# Query first 10 posts
for postId
in
{
1 ..
10
} ;
do
# Make API call to fetch emails of this posts's commenters
response =$
( curl
" ${endpoint} ?postId= ${postId} "
)
# Use jq to parse the JSON response into an array
allEmails+=
( $
( jq
'.[].email'
<<<
" $response "
)
)
done
请注意,我在使用jq
工具从命令行解析JSON。 jq
的语法超出了本文的范围,但是我强烈建议您对此进行研究。
正如您可能想象的那样,在无数其他情况下,使用Bash数组也可以提供帮助,我希望本文概述的示例能为您提供一些思考的机会。 如果您还有其他要分享的实例,请在下面发表评论。
但是,等等,还有更多!
由于我们在本文中介绍了很多数组语法,因此以下是我们所介绍内容的摘要以及一些我们未介绍的更高级的技巧:
句法 | 结果 |
---|---|
arr=() | 创建一个空数组 |
arr=(1 2 3) | 初始化数组 |
${arr[2]} | 检索第三个元素 |
${arr[@]} | 检索所有元素 |
${!arr[@]} | 检索数组索引 |
${#arr[@]} | 计算数组大小 |
arr[0]=3 | 覆盖第一个元素 |
arr+=(4) | 附加值 |
str=$(ls) | 将ls 输出另存为字符串 |
arr=( $(ls) ) | 将ls 输出另存为文件数组 |
${arr[@]:s:n} | 检索starting at index s n个元素 |
最后一个想法
正如我们所发现的那样,Bash数组肯定具有奇怪的语法,但是我希望本文使您相信它们非常强大。 一旦掌握了语法,您就会发现自己经常使用Bash数组。
Bash还是Python?
哪个提出了问题: 什么时候应该使用Bash数组而不是其他脚本语言(例如Python)?
对我来说,这全都归结为依赖性-如果您仅使用对命令行工具的调用就可以解决当前的问题,那么不妨使用Bash。 但是,当您的脚本属于更大的Python项目的一部分时,您不妨使用Python。
例如,我们可以求助于Python来实现参数清除,但是最终只需要围绕Bash编写包装器即可:
import subprocess
all_threads =
[
1 ,
2 ,
4 ,
8 ,
16 ,
32 ,
64 ,
128
]
all_runtimes =
[
]
# Launch pipeline on each number of threads
for t
in all_threads:
cmd =
'./pipeline --threads {}' .format
( t
)
# Use the subprocess module to fetch the return output
p = subprocess.Popen
( cmd,
stdout =subprocess.PIPE,
shell =True
)
output = p.communicate
(
)
[
0
]
all_runtimes.append
( output
)
由于在此示例中没有解决命令行问题,因此最好直接使用Bash。
该换个无耻的插头了
如果您喜欢这篇文章,那么还有更多的来历! 在此处注册以参加OSCON ,我将在2018年7月17日举行现场编码研讨会“ 你不知道Bash ”。没有幻灯片,没有点击器-只是你我在命令行中打字,探索了奇妙的世界巴什。
本文最初出现在Medium上 ,经许可重新发布。
翻译自: https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays