Лучше ли цикл for с использованием массивов, чем использование разделения полей для простой переменной?

Лучше ли цикл for с использованием массивов, чем использование разделения полей для простой переменной?

У меня открыто несколько приложений. Работаетwmctrlи перенаправляем вывод вawkвыводит список идентификаторов окон (исключая «закрепленные» окна) следующим образом:

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

Я могу отправить этот выводwmctrlчтобы закрыть все эти окна:

  • окна без содержимого, которое необходимо сохранить, и окна, которые не требуют ответа, будут закрыты без моего ведома, но

  • Такие окна, как окна редакторов с несохраненным содержимым или терминалов, на которых запущен процесс, будут закрыты «изящно»: соответствующее приложение откроет окно, позволяющее мне сохранить изменения или отменить изменения, или сообщит мне о процессе, который все еще выполняется.

Работает следующий скрипт, назначенный подходящей комбинации клавиш:

#!/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

Удалите echos, как только убедитесь, что все работает правильно.

Как отмечено в комментариях, 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

Можно обойтись и без массива.МФСto newline позволит forзацикливать строки, тогда вы можете использовать unsetIFS внутри цикла, не влияя на сам цикл.

#!/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)

Связанный контент