Best way to divide in bash using pipes?

2020-05-23 04:25发布

问题:

I'm just looking for an easy way to divide a number (or provide other math functions). Let's say I have the following command:

find . -name '*.mp4' | wc -l

How can I take the result of wc -l and divide it by 3?

The examples I've seen don't deal with re-directed out/in.

回答1:

Using bc:

$ bc -l <<< "scale=2;$(find . -name '*.mp4' | wc -l)/3"
2.33

In contrast, the bash shell only performs integer arithmetic.

Awk is also very powerful:

$ find . -name '*.mp4' | wc -l | awk '{print $1/3}'
2.33333

You don't even need wc if using awk:

$ find . -name '*.mp4' | awk 'END {print NR/3}'
2.33333


回答2:

Edit 2018-02-22: Adding shell connector

There is more than 1 way:

Depending on precision required and number of calcul to be done! See shell connector further!

Using bc (binary calculator)

find . -type f -name '*.mp4' -printf \\n | wc -l | xargs printf "%d/3\n" | bc -l
6243.33333333333333333333

or

echo $(find . -name '*.mp4' -printf \\n | wc -l)/3|bc -l
6243.33333333333333333333

or using bash, result in integer only:

echo $(($(find . -name '*.mp4' -printf \\n| wc -l)/3))
6243

Using bash interger builtin math processor

res=000$((($(find  . -type f -name '*.mp4' -printf "1+")0)*1000/3))
printf -v res "%.2f" ${res:0:${#res}-3}.${res:${#res}-3}
echo $res
6243.33

Pure bash

With recent 64bits bash, you could even use @glennjackman's ideas of using globstar, but computing pseudo floating could be done by:

shopt -s globstar
files=(**/*.mp4)
shopt -u globstar
res=$[${#files[*]}000/3]
printf -v res "%.2f" ${res:0:${#res}-3}.${res:${#res}-3}
echo $res
6243.33

There is no fork and $res contain a two digit rounded floating value.

Nota: Care about symlinks when using globstar and **!

Introducing shell connector

If you plan to do a lot of calculs, require high precision and use bash, you could use long running bc sub process:

mkfifo /tmp/mybcfifo
exec 5> >(exec bc -l >/tmp/mybcfifo)
exec 6</tmp/mybcfifo
rm /tmp/mybcfifo

then now:

echo >&5 '12/34'
read -u 6 result
echo $result
.35294117647058823529

This subprocess stay open and useable:

ps --sid $(ps ho sid $$) fw
  PID TTY      STAT   TIME COMMAND
18027 pts/9    Ss     0:00 bash
18258 pts/9    S      0:00  \_ bc -l
18789 pts/9    R+     0:00  \_ ps --sid 18027 fw

Computing $PI:

echo >&5 '4*a(1)'
read -u 6 PI
echo $PI
3.14159265358979323844

To terminate sub process:

exec 6<&-
exec 5>&-

Little demo, about The best way to divide in bash using pipes!

Computing range {1..157} / 42 ( I will let you google for answer to the ultimate question of life, the universe, and everything ;)

... and print 13 result by lines in order to reduce output:

printf -v form "%s" "%5.3f "{,}{,}{,,};form+="%5.3f\n";

By regular way

testBc(){
    for ((i=1; i<157; i++)) ;do
        echo $(bc -l <<<"$i/42");
    done
}

By using long running bc sub process:

testLongBc(){ 
    mkfifo /tmp/mybcfifo;
    exec 5> >(exec bc -l >/tmp/mybcfifo);
    exec 6< /tmp/mybcfifo;
    rm /tmp/mybcfifo;
    for ((i=1; i<157; i++)) ;do
        echo "$i/42" 1>&5;
        read -u 6 result;
        echo $result;
    done;
    exec 6>&-;
    exec 5>&-
}

Let's see without:

time printf "$form" $(testBc)
0.024 0.048 0.071 0.095 0.119 0.143 0.167 0.190 0.214 0.238 0.262 0.286 0.310
0.333 0.357 0.381 0.405 0.429 0.452 0.476 0.500 0.524 0.548 0.571 0.595 0.619
0.643 0.667 0.690 0.714 0.738 0.762 0.786 0.810 0.833 0.857 0.881 0.905 0.929
0.952 0.976 1.000 1.024 1.048 1.071 1.095 1.119 1.143 1.167 1.190 1.214 1.238
1.262 1.286 1.310 1.333 1.357 1.381 1.405 1.429 1.452 1.476 1.500 1.524 1.548
1.571 1.595 1.619 1.643 1.667 1.690 1.714 1.738 1.762 1.786 1.810 1.833 1.857
1.881 1.905 1.929 1.952 1.976 2.000 2.024 2.048 2.071 2.095 2.119 2.143 2.167
2.190 2.214 2.238 2.262 2.286 2.310 2.333 2.357 2.381 2.405 2.429 2.452 2.476
2.500 2.524 2.548 2.571 2.595 2.619 2.643 2.667 2.690 2.714 2.738 2.762 2.786
2.810 2.833 2.857 2.881 2.905 2.929 2.952 2.976 3.000 3.024 3.048 3.071 3.095
3.119 3.143 3.167 3.190 3.214 3.238 3.262 3.286 3.310 3.333 3.357 3.381 3.405
3.429 3.452 3.476 3.500 3.524 3.548 3.571 3.595 3.619 3.643 3.667 3.690 3.714

real    0m10.113s
user    0m0.900s
sys     0m1.290s

Wow! Ten seconds on my raspberry-pi!!

Then with:

time printf "$form" $(testLongBc)
0.024 0.048 0.071 0.095 0.119 0.143 0.167 0.190 0.214 0.238 0.262 0.286 0.310
0.333 0.357 0.381 0.405 0.429 0.452 0.476 0.500 0.524 0.548 0.571 0.595 0.619
0.643 0.667 0.690 0.714 0.738 0.762 0.786 0.810 0.833 0.857 0.881 0.905 0.929
0.952 0.976 1.000 1.024 1.048 1.071 1.095 1.119 1.143 1.167 1.190 1.214 1.238
1.262 1.286 1.310 1.333 1.357 1.381 1.405 1.429 1.452 1.476 1.500 1.524 1.548
1.571 1.595 1.619 1.643 1.667 1.690 1.714 1.738 1.762 1.786 1.810 1.833 1.857
1.881 1.905 1.929 1.952 1.976 2.000 2.024 2.048 2.071 2.095 2.119 2.143 2.167
2.190 2.214 2.238 2.262 2.286 2.310 2.333 2.357 2.381 2.405 2.429 2.452 2.476
2.500 2.524 2.548 2.571 2.595 2.619 2.643 2.667 2.690 2.714 2.738 2.762 2.786
2.810 2.833 2.857 2.881 2.905 2.929 2.952 2.976 3.000 3.024 3.048 3.071 3.095
3.119 3.143 3.167 3.190 3.214 3.238 3.262 3.286 3.310 3.333 3.357 3.381 3.405
3.429 3.452 3.476 3.500 3.524 3.548 3.571 3.595 3.619 3.643 3.667 3.690 3.714

real    0m0.670s
user    0m0.190s
sys     0m0.070s

Less than one second!!

Hopefully, results are same, but execution time is very different!

My shell connector

I've published a connector function: Connector-bash on GitHub.com and shell_connector.sh on my own site.

source shell_connector.sh
newConnector /usr/bin/bc -l 0 0
myBc 1764/42 result
echo $result
42.00000000000000000000


回答3:

find . -name '*.mp4' | wc -l | xargs -I{} expr {} / 2

Best used if you have multiple outputs you'd like to pipe through xargs. Use{} as a placeholder for the expression term.



回答4:

Depending on your bash version, you don't even need find for this simple task:

shopt -s nullglob globstar
files=( **/*.mp4 )
dc -e "3 k ${#files[@]} 3 / p"

This method will correctly handle the bizarre edgecase of filenames containing newlines.



标签: linux bash math