Parsing the result of a command in a Linux bash script

1

I am writing a small bash script in Linux to control Pulseaudio inputs/outputs.

What I want to do is to route all sink-inputs, except one, into one or another sink. The sink-input I don't want to route is this one:

pi@raspberrypi:~/fonction $ pactl list sink-inputs
Sink Input #36062
    Driver: protocol-native.c
    Owner Module: 2
    Client: 198
    Sink: 2
    Sample Specification: s16le 2ch 44100Hz
    Channel Map: front-left,front-right
    Format: pcm, format.sample_format = "\"s16le\""  format.rate = "44100"  format.channels = "2"  format.channel_map = "\"front-left,front-right\""
    Corked: no
    Mute: no
    Volume: front-left: 65536 / 100% / 0.00 dB,   front-right: 65536 / 100% / 0.00 dB
            balance 0.00
    Buffer Latency: 120000 usec
    Sink Latency: 236349 usec
    Resample method: n/a
    Properties:
        media.name = "ALSA Playback"
        application.name = "ALSA plug-in [snapclient]"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "32"
        application.process.id = "539"
        application.process.user = "snapclient"
        application.process.host = "raspberrypi"
        application.process.binary = "snapclient"
        application.language = "C"
        application.process.machine_id = "69e523231bb44f2e926a758a63cbb5b1"
        module-stream-restore.id = "sink-input-by-application-name:ALSA plug-in [snapclient]"

To move a sink-input to another sink I need to use this command:

pactl move-sink-input <sink-input id> <sink id>

So I have to parse the result of the first command to get the Sink Input #ID (contained in the first line) but I have to use a condition to check if module-stream-restore.id (last line) is really what I want to route.

I'd need something like:

if [ <last line> = 'sink-input name I need' ] 
then
  pactl move-sink-input <id from first line> <my sink name>
fi

I just don't know how to parse the command to get BOTH information.

Right now, I am able to get only the last line, or only the #ID.

 pactl list short sink-inputs|while read stream; do
    streamId=$(echo $stream )
    id=$(echo pactl list sink-inputs | grep module-stream-restore.id | grep -o '".*"' |sed 's/"//g')

    if [ "$streamId" != 'sink-input-by-application-name:ALSA plug-in [snapclient]' ]
    then
        pactl move-sink-input "$streamId" Snapcast
    fi

How do I proceed to parse the result of pactl list sink-inputs so I can read two elements, not just one, in each "block" (aka, each sink-input) of the result of that command in a bash script?

TurboGraphxBeige

Posted 2018-02-07T12:54:21.167

Reputation: 111

Why dont you save the sink input number in a variable? – Nifle – 2018-02-07T13:17:09.780

You can get ther first line and the last line with head -n1 and tail -n1 or sed '1!d' and sed '$!d' – Paulo – 2018-02-07T13:17:43.787

@Nifle, the sink-input number could change anytime. – TurboGraphxBeige – 2018-02-07T16:35:42.347

@Paulo, I'll give it a try tonight. It might be just what I need. – TurboGraphxBeige – 2018-02-07T16:36:45.543

Answers

2

The problem is that variables set in the do loop are not maintained from one pass to the next.

The simple solution is to save the list output to a temporary file, then scan it twice:-

pactl list short sink-inputs >temp.lst
streamId=$(sed -n 's/^Sink Input #\(.*\)$/\1/p' <temp.lst)
id=$(sed -n 's/.*module-stream-restore.id = "\(.*\)"$/\1/p' <temp.lst)
rm temp.lst 

You now have the two variables you need both set. It's not a very elegant solution, but it's straightforward and easy to understand and maintain. Note that I have used sed to perform the grep functions: for a guide to how this works, see here.

However, you can avoid the temporary file with a more convoluted solution:-

Ids="$(pactl list short sink-inputs | \
       sed -n -e 's/^Sink Input #\(.*\)$/\1/p' \
              -e 's/.*module-stream-restore.id = "\(.*\)"$/\1/p')"

You now have both variables in Ids, separated by a new-line: you can split them with:-

streamId="${Ids%$'\n'*}"
Id="${Ids#*$'\n'}"

If you don't like temporary variables either, you can use Id instead of Ids!

I have tested this by copying your list output and using this instead of a real pactl command, so it should all work with a live command.

AFH

Posted 2018-02-07T12:54:21.167

Reputation: 15 470

2

The concept:

  1. Invoke pactl … with LC_ALL=C set to avoid localized output (you apparently don't need this but in general case people do).
  2. Use egrep to discard irrelevant lines. You want to get pairs of lines that look like this:

    Sink Input #12345
            module-stream-restore.id = "whatever"
    
  3. Assuming the lines go in pairs, read them two by two with proper IFS (# for the first line in a pair, = for the second) to extract relevant data. Use dummy variables for parts you don't need.

  4. Work with extracted data. Note that IFS='=' for the second line in a pair will extract everything after the =, i.e. the adjacent space and double quotes as well. This is why in the code (below) I match against ' "sink-… [snapclient]"', not just 'sink-… [snapclient]'.

The code:

LC_ALL=C pactl list sink-inputs |
egrep "^Sink Input #|module-stream-restore.id = " |
while IFS='#' read dummy id && IFS='=' read dummy restid; do
 if [ "$restid" = ' "sink-input-by-application-name:ALSA plug-in [snapclient]"' ] ; then
  printf 'Match for ID %s.\n' "$id"
  # do something
 else
  printf 'No match for ID %s.\n' "$id"
  # do something else
 fi
done

Kamil Maciorowski

Posted 2018-02-07T12:54:21.167

Reputation: 38 429