У меня открыто несколько приложений. Работает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
Удалите 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
Можно обойтись и без массива.МФСto newline позволит 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)