配列を使用する 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も効果があることがわかりました。

どちらか一方を他方より優先する理由はあるでしょうか?


「sticky」と「gracefully」は からの用語です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

ただし、テストの合間にキャッシュされたバッファをフラッシュすることが重要です。

2 つのループの速度がほぼ同じである場合は、読みやすさが最も良いループを選択します。

ただし、この質問の範囲では、ほとんどの時間がユーザー入力の待機に費やされ、ほとんどのユーザーに対して最大 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

より短く、より読みやすいスクリプトを書くには、配列を使用する 2 つの方法があります。

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

配列なしの2番目:

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

私は配列メソッドを好みます。配列メソッドについてもっと学び、できるだけ多く使用したいからです。ただし、選択はあなた次第です。

答え4

配列なしでも管理できます。設定IFSC の改行を使用すると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)

関連情報