Execute a command from a variable inside a while loop or export variable out of while loop

1.1k views Asked by At

I've written a script with a while loop, not realising the difficulty of getting a variable out of said while loop.

What I want to do is to run the command the while loop saves in the 'stream' variable (see code below) after the while loop has completed as many loops as needed. I'm using this script to detect which game controllers are connected and creating a command to initialize moonlight, a program for the raspberry pi which lets me stream through Nvidia Game Stream, with different controller types.

#!/bin/bash

stream="moonlight stream -1080 -app Steam"
device=none

event=0

Player="-input /dev/input/event"

XbElite="-mapping /opt/retropie/configs/moonlight/XboxOne.map"
PlSt3="-mapping /opt/retropie/configs/moonlight/PS3.map"

XboxElite="N\:\ Name\=\"Microsoft\ X\-Box\ One\ Elite\ pad\""
PS3="N\:\ Name\=\"PLAYSTATION\(R\)3\ Controller\""

cat /proc/bus/input/devices | \
while read CMD; do
    line=$(echo "$CMD" | sed 's/ /\\ /g; s/:/\\:/g; s/(/\\(/g; s/)/\\)/g; s/-/\\-/g; s/=/\\=/g')
    if [ "$line" = "$XboxElite" ]; then
        echo Xbox
        device=xboxElite
        if [ "$device" = "xboxElite" ]; then
            stream="$stream $XbElite $Player$event"
            event=$((event+1))
            device=none
        fi
    elif [ "$line" = "$PS3" ]; then
        echo PS3
        device=ps3
        if [ "$device" = "ps3" ]; then
            stream="$stream $PlSt3 $Player$event"
            event=$((event+1))
            device=none
        fi
    fi
done
echo $stream #put here as a placeholder to see the final command in the variable. Currently printing: 'moonlight stream -1080 -app Steam'

I realise this probably isn't the cleanest way of doing this or that the code is even close to as well written as it could be, but as I have little previous experience with coding of any sort, this is what I could manage for the time being.

Any help would be greatly appreciated!

2

There are 2 answers

4
Mark Reed On

Well, the way to run it as a command would be to just remove the echo:

done
$stream

... except that you can't, because the while loop is on the right side of a pipe. That means it runs in a "subshell" and can't have side effects back in its parent shell (outside of the loop); after the loop exits, $stream will be back to what it was before the loop.

In order to keep the loop in the same environment as the surrounding code instead of its own subshell, you need to read from the file directly rather than using a pipe:

# no `cat` command here.  No pipe.
while read CMD; do
...
done </proc/bus/input/devices #this replaces the `cat |`.
$stream

In the general case, when building a command dynamically to run later, you are going to run into problems with quoting or spaces or other special characters. It's easiest to avoid those issues if you build the command in an array instead of a string. The syntax for building and running a command inside an array looks like this:

stream=(moonlight stream -1080 -app Steam)
...
while read CMD; do
  ...
  stream+=("$XbElite" "$Player$event")
  ...
  stream+=("$PlSt3" "$Player$event")
...
done </proc/bus/input/devices
"${stream[@]}"
0
agc On

What Mark Reed said, and also the main loop can be simplified:

sed 's/\([-:\( )=]\)/\\&/g' /proc/bus/input/devices | \
while read line; do
    if [ "$line" = "$XboxElite" ]; then
        echo Xbox
        stream="$stream $XbElite $Player$event"
        event=$((event+1))
    elif [ "$line" = "$PS3" ]; then
        echo PS3
        stream="$stream $PlSt3 $Player$event"
        event=$((event+1))
    fi
done