1

Can someone explain to me what's going on here?

Script:

#!/bin/sh
SKIP="unity-launcher|unity-panel|unity-dash|Hud|XdndCollectionWindowImp|Desktop"
WINS=()
wmctrl -l | grep -Ev " (${SKIP})" | cut -d \  -f 1 | while read window; do
    WINS=( ${WINS[@]} $window )
    echo "Found window: $window; New size: ${#WINS[@]}"
done
echo "Total window count: ${#WINS[@]}"
echo "Window IDs:"
for i in "${WINS[@]}"; do
    echo "  $i"
done

Output:

Found window: 0x0380000a; New size: 1
Found window: 0x038002ae; New size: 2
Found window: 0x03800a33; New size: 3
Found window: 0x03000001; New size: 4
Found window: 0x04c00004; New size: 5
Total window count: 0
Window IDs:

Expected:

Found window: 0x0380000a; New size: 1
Found window: 0x038002ae; New size: 2
Found window: 0x03800a33; New size: 3
Found window: 0x03000001; New size: 4
Found window: 0x04c00004; New size: 5
Total window count: 5
Window IDs:
    0x0380000a
    0x038002ae
    0x03800a33
    0x03000001
    0x04c00004

Essentially, at the end of the while loop, the WINS array is getting reset somehow, and I have no idea why. Is there some weird scoping thing in bash I'm unaware of?

Fordi
  • 502
  • 4
  • 7
  • That doesn't look like `/bin/sh` code; maybe instead use `/usr/bin/env bash` to find that shell, if it is installed? – thrig Aug 04 '16 at 15:07
  • Right, `/bin/sh` does not have arrays. Use `/bin/bash` – glenn jackman Aug 04 '16 at 15:10
  • @Kusalananda that's not really a good duplicate to point to, as there isn't really an answer (for this OP) to be found there... I'm pretty sure we do have a better duplicate, though. Somewhere. Just need to find it... – derobert Aug 04 '16 at 15:32
  • @glennjackman, that's closer to `zsh` syntax than `bash` syntax. That script would work with `#! /bin/zsh -` or `#! /bin/ksh93 -` as the she-bang. – Stéphane Chazelas Aug 04 '16 at 15:37
  • @derobert I'm fairly certain that is comes up every once in awhile, but that was the best I could find. There are loads of these on SO... – Kusalananda Aug 04 '16 at 15:47
  • 3
    @Kusalananda [Why is my variable local one 'while read' loop, but not in another seemingly similar loop](http://unix.stackexchange.com/questions/9954/why-is-my-variable-local-one-while-read-loop-but-not-in-another-seemingly-sim) looks better. – derobert Aug 04 '16 at 15:48
  • @derobert _Much_ better. – Kusalananda Aug 04 '16 at 15:49

1 Answers1

5

Pipes create subshells, and these subshells can't change the values in the processes above them. Try:

while read window; do
    WINS=( ${WINS[@]} $window )
    echo "Found window: $window; New size: ${#WINS[@]}"
done < <(wmctrl -l | grep -Ev " (${SKIP})" | cut -d \  -f 1)
  • You missed a space between the '<'s, but yeah you've the right answer. I was about to post the same myself after I'd done some research, but you got more brevity than I did. For others: The feature Jeff is using here is called 'process substitution'. What's going on is that, in my piped example, the entire loop runs in a subprocess; using process substitution, the parenthesized list runs as a subshell in parallel with the loop, while the loop itself remains in the main process; the output of the pipe list becomes stdin for the preceding command. – Fordi Aug 04 '16 at 15:20
  • Corrected. Thx. –  Aug 04 '16 at 15:23
  • Note that ksh88 and ksh93 (but _not_ pdksh), and zsh in some modes would run the last part of the pipe in the current shell; `echo hello | read a` acts differently in the different shells. – Stephen Harris Aug 04 '16 at 15:57