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?

marked as duplicate by Kusalananda, derobert, PersianGulf, Jeff Schaller, roaima Aug 4 '16 at 22:14

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 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 4 '16 at 15:07
  • Right, /bin/sh does not have arrays. Use /bin/bash – glenn jackman Aug 4 '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 4 '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 4 '16 at 15:37
  • 3
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 4 '16 at 15:20
  • Corrected. Thx. – Tomasz Aug 4 '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 4 '16 at 15:57

Not the answer you're looking for? Browse other questions tagged or ask your own question.