Um loop for usando matrizes é melhor do que usar a divisão de campos em uma variável simples?

Um loop for usando matrizes é melhor do que usar a divisão de campos em uma variável simples?

Tenho vários aplicativos abertos. Correndowmctrle canalizando a saída paraestranholista os IDs das janelas (excluindo janelas "fixas") assim:

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

Posso enviar esta saída parawmctrlpara fechar todas essas janelas:

  • janelas sem conteúdo que precisa ser salvo e janelas que não precisam de resposta serão fechadas sem me perguntar, mas

  • janelas como as de editores com conteúdo não salvo ou terminais executando um processo serão fechadas "normalmente": a respectiva aplicação apresentará uma janela que me permite salvar ou descartar alterações ou me informar sobre um processo que ainda está em execução.

O seguinte script, atribuído a um atalho adequado, funciona:

#!/bin/bash

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

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

Descobri que o mais simples (para mim) for i in $listtambém funciona.

Existe alguma razão para preferir um ao outro?


"pegajoso" e "graciosamente" são termos de man wmctrl.

Responder1

No seu script $listé o mesmo que ${list[@]}.

A última é uma sintaxe de array, mas no seu script é uma variável normal.


Como você não tem espaços em branco nos wmctlitens de saída, não precisa de um array e usar $listé perfeitamente adequado.


Se issoeraum array, $listseria apenas o primeiro item do array (=> item1) e ${list[@]}se estenderia a todos os itens (=> item1 item2 item3).

Mas o que você realmente queria se realmenteerauma matriz é "${list[@]}"(com aspas) que se estende até "item1" "item2" "item3", portanto, não engasgaria com espaços em branco.


(Ler)

Responder2

Um whileloop geralmente é mais adequado do que um forloop para processar a saída do comando, permitindo processar linhas diretamente em vez de armazená-las em uma listaouvariedade.

Neste caso, permite evitar awkcompletamente o comando:

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

Remova o echos quando estiver satisfeito por estar fazendo a coisa certa.

Conforme observado nos comentários, xargsé outra opção - mas fica complicado quando você deseja fazer mais de uma coisa com cada arquivo arg.

Responder3

Resposta ao título original

O título original perguntava "que tipo de loop for é melhor".

Para mim, o melhor método é o mais rápido. Para descobrir, acrescente o timecomando ao seu script ou função. Alguns exemplos:

$ time du -s

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

$ time ls

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

É importante liberar buffers em cache entre os testes:

Se dois loops tiverem aproximadamente a mesma velocidade, escolherei aquele com melhor legibilidade.

O escopo desta questão torna a velocidade irrelevante porque a maior parte do tempo é gasta aguardando a entrada do usuário e há apenas um máximo de 10 janelas abertas para a maioria das pessoas.


Resposta ao corpo da pergunta

Outras respostas se concentram em reescrever o roteiro, então também darei meus dois centavos.

A linha:

list=$(wmctrl -l | awk ' !/-1/ { print $1 } ')
  • está malformado se a intenção for ser uma matriz
  • listé genérico e não descritivo

Então eu usaria:

Windows=( $(wmctrl -l | awk ' !/-1/ { print $1 } ') )
  • O conjunto externo de () informa ao bash/shell que tudo dentro é um elemento de array delineado por espaços.
  • Estamos falando do Windows, portanto é um nome de array descritivo.
  • Windows é plural, portanto a convenção de nomenclatura ajuda a identificar que é uma matriz.

A linha:

wmctrl -i -a $i
  • -ie -apode ser combinado em -ia.
  • $inão é descritivo, eu usaria $Window.

Existem duas maneiras de escrever um script mais curto e mais legível, primeiro com um array:

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

segundo sem uma matriz:

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

Prefiro o método array porque estou tentando aprender mais sobre eles e quero usá-los o máximo possível. A escolha é sua, no entanto.

Responder4

Você pode gerenciar sem um array. ContextoSEpara nova linha permitirá forfazer loop de linhas, você pode então usar unseto IFS dentro do loop sem afetar o loop em si.

#!/bin/bash

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

(redefinindo oparâmetros posicionaisé um truque legal para dividir uma linha em campos).

se você precisar usar um array, você pode usararquivo de mapae aproveite a função de retorno de chamada para criar algo semelhante a um loop. Para um pequeno conjunto de iterações pode ser uma vantagem usar a chamada de função mais simples.

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

(versão longa):

#!/bin/bash

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

informação relacionada