使用另一个Bash命令的输出的Bash命令

3

我在Google Cloud上有一组机器。 从我的本地主机:

gcloud compute instance-groups list-instances workers
OUTPUT:
NAME              ZONE           STATUS
workers-lya2    us-central1-a     RUNNING 
workers-23d4     us-central1-a     RUNNING 
...
workers-3asd3     us-central1-a     RUNNING    

我想要从列表中随机选择一个工作人员的名字(假设是workers-23d4),并且它所在的区域是us-central1-m,然后将其粘贴到这个命令中:

gcloud compute --project "my-project" ssh --zone "<zone_name_from_first_command> "<machine_name_from_first_command>"

我对Bash不太熟悉,请帮忙。


你需要哪一个?随机的还是匹配一个字符串的特定的? - hansaplast
1
抱歉表述不清楚,我会编辑问题。我的意思是随机选择一个。 - WebQube
谢谢,现在清楚了。我把我对这个问题的尝试添加为答案。 - hansaplast
3个回答

3
下面的命令从gcloud命令的输出中随机选择一行(不包括标题),然后将前两个“单词”存储到machinezone变量中:
read -r machine zone unused <<< $(
  gcloud compute instance-groups list-instances workers | \
    perl -e '@_ = <>; shift @_; print $_[rand @_]'
)

运行这个命令后,您就可以使用machinezone变量了,例如:

gcloud compute --project "my-project" ssh --zone "$zone" "$machine"

解释

perl 命令使用钻石操作符 <> 从标准输入读取所有行并将其存储在 @_ 数组中。然后,shift 函数从 @_ 中移除第一个项目。 rand@_ 返回介于零和 @_ 中项目数之间的随机十进制数。在索引上下文中,该十进制数会被隐式转换为整数。因此,$_[rand @_] 的结果是 @_ 的一个随机项,即来自 gcloud 命令输出的一行随机内容。

使用命令替换 捕获 gcloudperl 命令的输出,并通过here string 传递给 read 命令。

第一段中我用引号引起了 单词,因为 shell 根据 IFS (输入字段分隔符) 变量将字符序列解释为单词。因此,来自 here string 的以 IFS 为分隔符的单词被分配给 machine(第一个单词)、zone(第二个单词)和 unused(该行的其余部分)变量。

-r 选项禁用反斜杠的特殊含义。换句话说,当给出此选项时,read 不会尝试解释输入中的转义序列。

大量行的情况

请注意,此解决方案意味着 gcloud 命令的输出相对较小,即足够小,可以将整个文件读入数组。这个操作速度很快,但需要更多的内存,与使用 while <> 循环逐行读取相比。如果输出非常大或内存非常有限,则以下是另一种解决方案:

read -r machine zone unused <<< $(
  gcloud compute instance-groups list-instances workers | \
    perl -e '<>; $. = 0; rand($.) < 1 && ($line = $_) while <>; print $line'
)

其中 <> 读取头部信息,$. 是内置变量,保存当前行号;其余内容来源于此教程


做得很好,特别是第二个内存高效的 perl 命令。虽然在这种情况下没有区别,但我仍建议双引号引用命令替换以促进良好习惯。 - mklement0

2
gcloud compute instance-groups list-instances workers | grep -v "^NAME"  | shuf -n 1 | awk '{print $1, $2}' | 
while read machine zone; do
    export SELECTED_MACHINE="$machine"
    export SELECTED_ZONE="$zone"
done
gcloud compute --project "my-project" ssh --zone "$SELECTED_ZONE" "$SELECTED_MACHINE"
  • grep -v "^NAME"会去掉以NAME开头的所有行(假设你只想去掉第一行)
  • shuf从剩余行中随机选择一行
  • awk '{print $1, $2}'将该行分割成空格并打印出第一列和第二列
  • while read读取awk的输出到变量$machine$zone

更新:上述代码适用于zsh,但不适用于bash,因为bash在子shell中运行管道(而zsh不会),export只将变量传递给子进程,而不是父进程。以下脚本通过使read在父进程中运行来解决此问题:

machine_zone=$(gcloud compute instance-groups list-instances workers | 
              grep -v "^NAME"  | shuf -n 1 | awk '{print $1, $2}')
read machine zone <<< $machine_zone
gcloud compute --project "my-project" ssh --zone "$zone" "$machine"

@hansplast 谢谢你。伪终端将不会被分配,因为标准输入不是终端。 - WebQube
2
++ 用于提取随机行的部分很好,但由于您的read命令在子shell中执行(由于使用了管道),因此第二个gcloud命令将无法看到您定义的变量 - 无论您是否导出它们; 此外,您不需要循环来读取awk的单行输出。 - mklement0
@mklement0 感谢您的解释。实际上,每个管道命令都是一个子shell(如man bash所述),它将stdin连接到前一个命令的stdout,因此stdin不是伪终端。我猜当使用管道时,没有绕过这个问题的方法,对吗? - hansaplast
感谢您的更新。zsh 也不会将子进程环境变量传递给当前进程(没有任何 shell 这样做),但它不会在 子 shell 中运行 read 命令 - 这就是为什么它能够工作的原因 - 在任何情况下都不需要环境变量。您仍然不需要使用 循环 来读取值。顺便说一句:最好使用 命令替换语法 $(...) 而不是旧语法 `...` - mklement0
1
@mklement0 哦,那很有道理。我更新了我的解释,并且用 $(...) 替换了反引号,感谢链接。我从来没有理解过为什么 $(..) 比反引号更受欢迎,现在我懂了。 - hansaplast
显示剩余4条评论

2
如果要从中提取随机行的输入很大,并且您想避免将其完全读入内存,就像“ shuf”一样,请考虑Ruslan Osmanov有用的回答中的第二个perl解决方案。
否则,hansaplast有用的回答使用基于“ shuf”的多实用程序方法易于理解,但它可以简化(截至本文撰写时存在缺陷)。
read -r machine zone _ < \ 
  <(gcloud compute instance-groups list-instances workers | tail +2 | shuf -n 1)

gcloud compute --project "my-project" ssh --zone "$zone" "$machine"

通过让read从进程替换(<(...))中读取输出,确保read当前 shell 中执行,这意味着它创建的变量对剩余命令是可见的,特别是第二个 gcloud 命令。 相比之下,如果使用管道(gcloud ... | read ...),read会在一个子shell中执行,并且它创建的变量不会被后续命令看到。 tail +2跳过输入中的第一行(标题行)。 shuf -n 1从输入中提取1个随机选择的行。 请注意,将第三个变量命名为_,它接收了第一个2个以空格分隔的标记被读入$machine$zone之后的 (未使用的)rest输入行。 如果我们只指定了machinezone,那么$zone不仅会接收第二个标记,而且还会接收剩余的输入行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接