2

I want to redirect this command to a variable:

{
    printf 'scan on\n\n'
    sleep 10
    printf 'quit \n\n'  
} | bluetoothctl

I tried to do this:

variable=$(printf 'scan on\n\n' sleep 10 printf 'quit \n\n' | bluetoothctl)
echo $variable

but I am getting blank output. I also tried to split it:

variable=$(printf 'scan on\n\n' | bluetoothctl)
sleep 10
printf 'quit \n\n'| bluetoothctl
echo $variable

but this time I only get

Agent registered
[bluetooth]# quit

How do I redirect the output of the first command which is printed to stdout when you run it, into a variable?

cheshire
  • 157

3 Answers3

7

Just:

variable=$(
  {
    printf 'scan on\n\n'
    sleep 10
    printf 'quit \n\n'  
  } | bluetoothctl
)

The inside of command substitutions can be any shell code¹ and doesn't have to be on one line.

Here, you want to capture the output of the whole pipeline (in effect, that will be the output of bluetoothctl since all the other commands have their stdout redirected to the pipe that goes to bluetoothctl).

You could also capture the output of bluetoothctl only with:

{
  printf 'scan on\n\n'
  sleep 10
  printf 'quit \n\n'  
} | variable=$(bluetoothctl)

But in bash, for that to work, you'd need to set the lastpipe option (shopt -s lastpipe, only works for non-interactive shell instances), for that last pipe component not to be run in a subshell.

Here however, you don't need the pipeline, you can just do:

variable=$(bluetoothctl --timeout 10 scan on)

If you're going to display the contents of the variable, remember the syntax is:

printf '%s\n' "$variable"

not echo $variable.

More details on that at:

Here the output of bluetoothctl contains colouring escape sequences as well as escape sequences used to clear the contents of lines. If you leave $variable unquoted, its contents will be subject to split+glob resulting in a random list of words to pass to echo which echo will output space-separated. So you'll end up with everything on one line and if the last word ends in a clear-line sequence (which it does in my tests: \r\e[K being Carriage-Return followed by the Clear-To-End-Of-Line sequence), you'll just see a blank line.


¹ though some shells including bash used to choke on code with unmatched parenthesis like when the code included case statements like case $x in foo) or comments with unbalanced ) which the shell parser was mistaking for the ) closing the command substitution.

  • for the first one I get the blank one again. For the second one I am getting the ouput only if I execute shopt -s lastpipe before it, but output is missing on some places and is weirdly only on one line. I don't know if I did something wrong – cheshire Dec 04 '20 at 15:56
  • 1
    @cheshire, probably down to you using echo $variable instead of printf '%s\n' "$variable". See Why is printf better than echo? and When is double-quoting necessary? – Stéphane Chazelas Dec 04 '20 at 16:02
  • You were right. Just one more question, is there some problem in simply doing it like the answer that I accepted suggests? – cheshire Dec 04 '20 at 16:07
  • 1
    @cheshire, whether you use ; or newline to separate the command substitutions will not make any difference in that case. Whether using echo will cause problem or not in that case depends on a number of parameters detailed in the Q&A I linked to. Using echo to output arbitrary data is bad practice in general. – Stéphane Chazelas Dec 04 '20 at 16:10
2

The mistake is the lack of semicolons (that are otherwise equivalent to newlines). And if you want to see the structure of the output, add double-quotes. Try:

variable=$( { printf 'scan on\n\n' ; sleep 10 ; printf 'quit \n\n' ; } | bluetoothctl)
echo "$variable"
David G.
  • 1,369
1

A possible alternative could be a simple expect script:

#!/usr/bin/expect

spawn bluetoothctl

send "scan on\r"

sleep 10

send "quit \r"

expect eof

\r equals "Return / pressing enter". Make executable and run.

FelixJN
  • 13,566
  • I guess it could be good idea, but I am not familiar with manipulating output or storing it in expect – cheshire Dec 04 '20 at 15:57