使用数组的 for 循环是否比在简单变量上使用字段拆分更好?

使用数组的 for 循环是否比在简单变量上使用字段拆分更好?

我打开了几个应用程序。正在运行控制端并将输出传输至awk列出窗口 ID(不包括“粘性”窗口),如下所示:

$ wmctrl -l | awk ' !/-1/ { print $1 } '
0x00a00018
0x04800005
0x04e00005
0x04400003
0x05000003
0x0540002b
0x05a00012
0x05800002
0x05c00003
$ 

我可以将此输出发送到控制端关闭所有这些窗口:

  • 没有需要保存的内容的窗口和不需要响应的窗口将会关闭,而无需询问我,但

  • 诸如未保存内容的编辑器窗口或正在运行进程的终端窗口将被“优雅地”关闭:相应的应用程序将显示一个窗口,允许我保存更改或放弃更改,或者通知我仍在运行的进程。

分配给合适快捷方式的以下脚本可以起作用:

#!/bin/bash

list=$(wmctrl -l | awk ' !/-1/ { print $1 } ')

for i in ${list[@]}
do
    wmctrl -i -a $i
    wmctrl -i -c $i
done

我发现(对我来说)更简单的方法for i in $list也有效。

有什么理由选择其中一个而不是另一个呢?


“粘性”和“优雅地”是来自的术语man wmctrl

答案1

在您的脚本中$list与 相同${list[@]}

后者是数组语法,但在您的脚本中它是一个普通变量。


由于您的输出项中没有空格wmctl,因此您不需要数组,使用$list就完全没问题。


如果它曾是一个数组,$list只会是数组的第一个项(=> item1)并且 ${list[@]}会扩展到所有项(=> item1 item2 item3)。

但你真正想要的是曾是数组是"${list[@]}"(带引号),其扩展到"item1" "item2" "item3",因此它不会被空格阻塞。


答案2

循环while通常比循环更适合for处理命令输出,允许您直接处理行而不是将它们存储在列表中或者大批。

在这种情况下,它允许您完全避免该awk命令:

wmctrl -l | while read -r id dt stuff; do 
  case $dt in 
    -1) continue
        ;; 
     *) echo wmctrl -i -a "$id"
        echo wmctrl -i -c "$id"
        ;; 
  esac
done

echo一旦您确认它做了正确的事情,就可以删除s。

正如评论中所述,xargs这是另一种选择 - 但当您想对每个选项执行多件事时,就会变得棘手arg

答案3

回答原标题

原标题问“什么类型的for循环比较好”。

对我来说,最好的方法就是最快的方法。要找出答案,请将time命令添加到脚本或函数中。以下是一些示例:

$ time du -s

real    0m0.002s
user    0m0.003s
sys     0m0.000s

$ time ls

real    0m0.004s
user    0m0.000s
sys     0m0.004s

然而,在测试之间刷新缓存缓冲区非常重要:

如果两个循环的速度大致相同,我会选择可读性最好的循环。

然而,这个问题的范围使得速度变得无关紧要,因为大部分时间都花在等待用户输入上,而且对于大多数人来说,最多只打开 10 个窗口。


问题正文的答案

其他答案侧重于重写脚本,因此我也会提供我的意见。

以下行:

list=$(wmctrl -l | awk ' !/-1/ { print $1 } ')
  • 如果目的是数组则格式不正确
  • list是通用的,不是描述性的

所以我会使用:

Windows=( $(wmctrl -l | awk ' !/-1/ { print $1 } ') )
  • 外部的 () 集合告诉 bash/shell 里面的所有内容都是由空格划定的数组元素。
  • 我们正在谈论的是 Windows,所以它是一个描述性的数组名称。
  • Windows 是复数,因此命名约定有助于识别它是一个数组。

以下行:

wmctrl -i -a $i
  • -i并且-a可以组合成-ia
  • $i是非描述性的,我会使用$Window它。

有两种方法可以编写更短、更易读的脚本,第一种是使用数组:

#!/bin/bash
Windows=( $(wmctrl -l | awk ' !/-1/ { print $1 } ' ) )
for Window in "${Windows[@]}" ; do wmctrl -ia $Window -c $Window ; done

第二个没有数组:

#!/bin/bash
Windows=$(wmctrl -l | awk ' !/-1/ { print $1 } ' )
for Window in $Windows ; do wmctrl -ia $Window -c $Window ; done

我更喜欢数组方法,因为我正在尝试更多地了解它们并希望尽可能多地使用它们。不过选择权在你手中。

答案4

你可以在不使用数组的情况下进行管理。设置独立财务顾问换行符将允许for循环行,然后您可以unset在循环内部使用 IFS,而不会影响循环本身。

#!/bin/bash

IFS=$'\n'
for i in $(wmctrl -l); do
    unset IFS
    set -- $i
    (($2 > -1)) && wmctrl -i -a $1 -c $1
done

(重置位置参数是将一行拆分成多个字段的巧妙技巧)。

如果需要使用数组,可以使用映射文件并利用回调函数来创建类似于循环的东西。对于一小组迭代,使用更简单的函数调用可能是一个优势。

mapfile -c 1 -C 'f(){ set -- $@; (($3 >= 0)) && wmctrl -i -a $2 -c $2; }; f' -t < <(wmctrl -l)

(长版):

#!/bin/bash

f(){
    set -- $@
    if (($3 > -1)); then
        wmctrl -i -a $2 -c $2
    fi
}
mapfile -c 1 -C f -t < <(wmctrl -l)

相关内容