How to assign a piped output as a variable while pipe is continuous - bash

I want to update the download status at every 5 second of a downloading file to my telegram bot. Also here I'm using bash.
aria2c $url --summary-interval=5 2>&1 | tee output.log | grep -oP "(\d+(\.\d+)?(?=%))"
This thing derive me download percentage after each 5 seconds. I want to use this download percentage for my bot to update it regularly. I tried these
aria2c $url --summary-interval=5 2>&1 | tee output.log | grep -oP "(\d+(\.\d+)?(?=%))" | { read text; curl -s "https://api.legram.org/bot${tg_token}/editMessageText" --data "message_id=${msg_id}&text=DOWNLOADED-${text}&chat_id=${ch_id}&parse_mode=HTML&disable_web_page_preview=True"; }
Try 2
aria2c $url --summary-interval=5 2>&1 | tee output.log | text=$(grep -oP "(\d+(\.\d+)?(?=%))") | curl -s "https://api.legram.org/bot${tg_token}/editMessageText" --data "message_id=${msg_id}&text=DOWNLOADED-${text}%&chat_id=${ch_id}&parse_mode=HTML&disable_web_page_preview=True"; }
But none works. Then for testing I tried this
aria2c $url --summary-interval=5 2>&1 | tee output.log | grep -oP "(\d+(\.\d+)?(?=%))" | { read text; echo "$text"; }
I just got one output at last(which might be the first download %), unlike what it should be. Can anyone get me the working code.

The problem is that you only run read (and then updates the status) once, so it reads a single line (and updates the status once). You need a loop, so it'll repeat the read+update process over & over. You can use a while loop to do this. If it should exit when there's no more input to process, make read the while condition:
aria2c $url --summary-interval=5 2>&1 |
tee output.log |
grep -oP "(\d+(\.\d+)?(?=%))" |
while read text; do
curl -s "https://api.legram.org/bot${tg_token}/editMessageText" --data "message_id=${msg_id}&text=DOWNLOADED-${text}&chat_id=${ch_id}&parse_mode=HTML&disable_web_page_preview=True"
done

Related

How to take a single line of input from a long running command, then kill it?

Is there a way to take one line of input from a stream, pass it on as an argument and kill the stream?
In pseudo-bash code:
tail -f stream | filter | take-one-and-kill-tail | xargs use-value
Edit: actual script so far is:
i3-msg -t subscribe -m '["window"]'| stdbuf -o0 -e0 jq -r 'select(.change == "new") | "\(.container.window)\n"' | head -0
and it has following (undesirable) behaviour:
$ i3-msg -t subscribe -m '["window"]'| stdbuf -oL -eL jq -r 'select(.change == "new") | "\(.container.window)\n\n"' | head -1
# first event happens, window id is printed
79691787
# second event happens, head -1 quits
$
You could run the command in subshell and kill that shell.
In this example I'm killing the stream after the first info message:
#!/bin/bash
( sudo stdbuf -oL tail -f /var/log/syslog | stdbuf -oL grep -m1 info ; kill $$ )
Note: ( pipeline ) will run the pipeline in a subshell. $$ contains the pid of the current shell. (Which is the subshell in the above example)
In the above example grep -m1 is ensuring that only one line of ouput is read/written before killing the pipe.
If your filter program does not support such an option like -m1, you could pipe to awk and exit awk after the first line of input. The remaining concept stays the same:
( sudo stdbuf -oL tail -f /var/log/syslog \
| stdbuf -oL grep info \
| awk '{print;exit}' ; kill $$)

How to output bash command to stdout and pipe to another command at the same time?

I'm working on a server and to show detailed GPU information I use these commands:
nvidia-smi
ps -up `nvidia-smi |tail -n +16 | head -n -1 | sed 's/\s\s*/ /g' | cut -d' ' -f3`
However as you can see, nvidia-smi is called twice. How can I make the output of nvidia-smi go to output and pipe to another command at the same time?
Use tee:
ps -up `nvidia-smi |tee /dev/stderr |tail -n +16 | head -n -1 | sed 's/\s\s*/ /g' | cut -d' ' -f3`
Since stdout is piped, you can't make a copy to it, so I picked stderr to show output.
If /dev/stderr is not available, use /proc/self/fd/2.

Catch output of several piped commands

Till today I was always able to find answer for my all bash questions. But now I stuck. I am testing 20TB RAID6 configuration working on LSI 9265.
I wrote script to create files from /dev/urandom and I am creating second to calculate md5 from all files with two addons.
One is to use time command to calculate md5sum execution time
Second is use pv command to show progress of each md5sum command
My command looks like this:
filename="2017-03-13_12-38-08"
/usr/bin/time -f "real read %E" pv $filename | md5sum | sed "s/-/$filename /"
This is example terminal printout:
/usr/bin/time -f "real read %E" pv $i | md5sum | sed "s/-/$i/"
1GiB 0:00:01 [ 551MiB/s] [==================================================================================================>] 100%
real read 0:01.85
f561af8cc0927967c440fe2b39db894a 2017-03-13_12-38-08
And I want to log it to file. I failed all tries using 2>&1, using tee, using brackets. I know pv uses stdErr but this doesnt help in finding solution. I can only catch "f561af8cc0927967c440fe2b39db894a 2017-03-13_12-38-08_done"
which is not enough.
This is the solution:
(time pv -f $filename | md5sum | sed "s/-/$filename/") 2>&1 | tee output.log
or equivalent but without printing into terminal only file to output.log
(time pv -f $filename | md5sum | sed "s/-/$filename/") > output.log 2>&1

Bash Subshell Variable Command not Found

I'm trying to run a command and interpret the results, but whatever I do I get a "command not found" error. Here's a representative version of my code:
devicename="emulator-5554"
search=$(adb devices | grep -w "$devicename" | grep -w device)
until $search; do
echo "Waiting..."
sleep 10
done
I've tried every variation that I can think of, including ...
search=$(adb devices | grep -w $devicename | grep -w device)
and
search=$(adb devices | grep -w ${devicename} | grep -w device)
..., but all return the same error.
How can I get the variable to be interpreted correctly?
The code you have runs the adb|grep|grep pipeline once only and stores the output in $search. Reading from $search doesn't re-run the pipeline.
Don't use variables to hold commands. Use functions.
search() {
adb devices | grep -w "$devicename" | grep -qw device
}
until search; do
echo "Waiting..."
sleep 10
done
Notice that I added -q to silence the final grep. You don't need to know what it found, just that it found something. Its exit code is all that matters; its output is irrelevant.
You could inline the function if you want.
until adb devices | grep -w "$devicename" | grep -qw device; do
echo "Waiting..."
sleep 10
done
Or you could make $devicename a parameter, if you wish.
search() {
adb devices | grep -w "$1" | grep -qw device
}
until search "$devicename"; do
echo "Waiting..."
sleep 10
done

Write mplayer's output to fifo and read it

I'm trying write simple notify app in bash. I want to read output from mplayer, parse it and display through notify-send.
I can get desired info from mplayer using this:
mplayer <url> | grep ICY
and then parse in using sed.
I create named pipe, tell mplayer to write it and then I'm reading from it. Unfortunately, it doesn't work. Here's my script:
$fifo=~/.rp/fifo
mkfifo $fifo
mplayer <url> 2>/dev/null | grep ICY 1> $fifo &
while read line < $fifo; do
echo $line
done
wait
Program keeps waiting to input from $fifo. I tried following in other terminal, while this script is running:
Run
echo "Test" > .rp/fifo
Terminal with running script shows "Test"
Run
echo "ICY" | grep ICY > .rp/fifo
also works.
Run
mplayer <url> | grep ICY > .rp/fifo
and it doesn't work.
Is I said above, the combination of mplayer | grep works fine. grep > $fifo works fine. I don't understand why mplayer | grep > $fifo doesn't work.
I suspect you might be experiencing the C library's fully buffered mode for streams. You don't say that you're running the GNU userspace, but if you are, you can look into stdbuf(1) to modify the buffering regime.
You might try first running just grep as a child of stdbuf(1), like this:
mplayer <url> | stdbuf -o L grep ICY > .rp/fifo
If that doesn't work, moar is bettar!
stdbuf -o 0 mplayer <url> | stdbuf -o L grep ICY > .rp/fifo
And if that still doesn't work, then it's possible that mplayer isn't writing to stdout, but directly to /dev/tty. In which case, you will need to read up on expect(1).
You could do unbuffered grep with:
$ mplayer ... 2>&1 | grep --line-buffered "ICY"
or better:
$ mplayer ... 2>&1 | sed -une 's/^.*ICY[^:]*: //p'
or even, why not (sed is very nice for grep and formatting),
this will grep ICY lines and even split line containing - in a first field of 30 chars length separed by a : from a second field:
$ mplayer ... 2>&1 |
sed -une "
/ICY/{
s/^.*ICY[^:]*:.*'\([^']*\)';/\1/;
s/^\(.*\) - /\1 - /;
s/^\(.\{30\}\) *- /\1: /;
p;
}"
could give something like:
Artist name : Song title
Other artist : Other song
Unsplited line
Artist : Title
I start mplayer in slave mode, using FIFO file.
mkfifo /tmp/mpfifo
mplayer -slave -input file=/tmp/mpfifo video.mp4
I am able to control the video player from another terminal.
echo "pause" >> /tmp/mpfifo
echo "volume 50" > /tmp/mpfifo
I want to get value (for example current position of playing video). So I tried:
echo "get_time_pos" > /tmp/mpfifo
But no value returned.
I searched for hours, but no success.
Then I thought to redirect mplayer output to a file:
mplayer -slave -input file=/tmp/mpfifo video.mp4 > /tmp/mpout.txt
After that when like following commands executed:
echo "get_time_pos" > /tmp/mpfifo
echo "get_property length" > /tmp/mpfifo
The outputs in /tmp/mpout.txt was like:
.......
.......
ANS_TIME_POSITION=113.6
ANS_length=2534.602031
If the result of each command would return to the command line would be very nice. Even it may need some works, the output file can be parsed, though.

Resources