Come si usa la divisione in virgola mobile in bash?


250

Sto cercando di dividere due larghezze di immagine in uno script Bash, ma bash mi dà 0come risultato:

RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))

Ho studiato la guida di Bash e so che dovrei usare bc, in tutti gli esempi in Internet che usano bc. In echoHo provato a mettere la stessa cosa nella mia SCALE, ma non ha funzionato.

Ecco l'esempio che ho trovato nei tutorial:

echo "scale=2; ${userinput}" | bc 

Come posso convincere Bash a darmi un galleggiante del genere 0.5?


9
Un commento per tutti coloro che cercano di fare aritmetica in virgola mobile nel tuo script, chiediti: ho davvero bisogno di aritmetica in virgola mobile? a volte puoi davvero andare d'accordo senza. Vedi, ad esempio, l'ultima parte di BashFAQ / 022 .
gniourf_gniourf

Risposte:


250

Non puoi. bash fa solo numeri interi; si deve delegare a uno strumento come bc.


9
come posso delegare uno strumento come bc a mettere la risposta nella variabile RISULTATO?
Medya Gh

2
quindi intendi come VAR = $ (echo "scale = 2; $ (($ IMG_WIDTH / $ IMG2_WIDTH))" | bc)?
Medya Gh

56
@Shevin VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)o VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")senza $ (()) (doppie parentesi); che viene ampliato dalla bash prima di eseguire il comando
Nahuel Fouilleul

4
Vero, ma è generalmente più probabile che awk sia già installato nel sistema.
CMCDragonkai

9
@NahuelFouilleul Hai la migliore risposta qui. Dovrebbe davvero essere la sua risposta e accettata come risposta. Questa particolare riga è stata incredibilmente utile: VAR = $ (bc <<< "scale = 2; $ IMG_WIDTH / $ IMG2_WIDTH")
David

204

Puoi farlo:

bc <<< 'scale=2; 100/3'
33.33

AGGIORNAMENTO 20130926 : puoi usare:

bc -l <<< '100/3' # saves a few hits
33.33333333333333333333

8
... e aggiunge molte cifre. Almeno sulla mia macchina questo yiels 33.33333333333333333333 mentre il primo dà 33.33.
Andreas Spindler

12
@AndreasSpindler Tipo di un vecchio post, ma nel caso qualcuno volesse saperlo, questo può essere modificato applicando il comando scale es. bc -l <<< 'scale=2; 100/3'
Martie

Fai attenzione se speri di ottenere un numero intero per un uso successivo in bash usando scale = 0. A partire dalla v1.06.95, bc, per qualche motivo, ignora la variabile di scala quando i numeri di input hanno una parte decimale. Forse questo è nei documenti, ma non sono riuscito a trovarlo. Prova: echo $ (bc -l <<< 'scale = 0; 1 * 3.3333')
Greg Bell

1
@GregBell La pagina man dice Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved.E c'è una nota extra per l' /operatore:The scale of the result is the value of the variable scale.
psmith

2
Grazie @psmith. È interessante notare che / dice "la scala del risultato è il valore della scala variabile" ma non così per la moltiplicazione. I miei esempi migliori: la bc <<< 'scale=1; 1*3.00001' scala è davvero 5 per qualche motivo, la bc <<< 'scale=1; 1/3.000001' scala è 1. È interessante notare che la divisione per 1 la rende dritta: la bc <<< 'scale=1; 1*3.00001/1'scala è 1
Greg Bell

142

bash

Come notato da altri, bashnon supporta l'aritmetica in virgola mobile, anche se potresti falsificarla con qualche trucco decimale fisso, ad esempio con due decimali:

echo $(( 100 * 1 / 3 )) | sed 's/..$/.&/'

Produzione:

.33

Vedi la risposta di Nilfred per un approccio simile ma più conciso.

Alternative

Oltre a quanto menzionato bce alle awkalternative ci sono anche i seguenti:

clisp

clisp -x '(/ 1.0 3)'

con output ripulito:

clisp --quiet -x '(/ 1.0 3)'

o tramite stdin:

echo '(/ 1.0 3)' | clisp --quiet | tail -n1

dc

echo 2k 1 3 /p | dc

calcolatore di genius cli

echo 1/3.0 | genius

ghostscript

echo 1 3 div = | gs -dNODISPLAY -dQUIET | sed -n '1s/.*>//p' 

gnuplot

echo 'pr 1/3.' | gnuplot

jq

echo 1/3 | jq -nf /dev/stdin

O:

jq -n 1/3

ksh

echo 'print $(( 1/3. ))' | ksh

lua

lua -e 'print(1/3)'

o tramite stdin:

echo 'print(1/3)' | lua

maxima

echo '1/3,numer;' | maxima

con output ripulito:

echo '1/3,numer;' | maxima --quiet | sed -En '2s/[^ ]+ [^ ]+ +//p'

nodo

echo 1/3 | node -p

ottava

echo 1/3 | octave

perl

echo print 1/3 | perl

python2

echo print 1/3. | python2

python3

echo 'print(1/3)' | python3

R

echo 1/3 | R --no-save

con output ripulito:

echo 1/3 | R --vanilla --quiet | sed -n '2s/.* //p'

rubino

echo print 1/3.0 | ruby

wcalc

echo 1/3 | wcalc

Con output ripulito:

echo 1/3 | wcalc | tr -d ' ' | cut -d= -f2

zsh

echo 'print $(( 1/3. ))' | zsh

unità

units 1/3

Con uscita compatta:

units --co 1/3

Altre fonti

Stéphane Chazelas ha risposto a una domanda simile su Unix.SX.


9
Bella risposta. So che è stata pubblicata alcuni anni dopo la domanda, ma merita di più di essere la risposta accettata.
Brian Cline

2
Se hai zsh disponibile, allora vale la pena considerare di scrivere la tua sceneggiatura in zsh invece di Bash
Andrea Corbellini

Bella lista. Soprattutto echo 1/3 | node -pè breve.
Johnny Wong

40

Migliorando un po 'la risposta di marvin:

RESULT=$(awk "BEGIN {printf \"%.2f\",${IMG_WIDTH}/${IMG2_WIDTH}}")

bc non viene sempre fornito come pacchetto installato.


4
Lo script awk ha bisogno di un exitper impedirgli di leggere dal suo flusso di input. Suggerisco anche di usare le -vbandiere di awk per prevenire la sindrome da stuzzicadenti pendente. Quindi:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
aecolley

2
Un modo più awkish per farlo sarebbe quello di leggere gli argomenti dal flusso di input: RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH ".
jmster

1
bcfa parte di POSIX, di solito è preinstallato.
fuz

questo ha funzionato per me usando git bash in Windows 7 ... grazie :)
The Beast

31

Puoi usare bc -ldall'opzione (la lettera L)

RESULT=$(echo "$IMG_WIDTH/$IMG2_WIDTH" | bc -l)

4
Se non includo il -lnel mio sistema, bc non esegue calcoli in virgola mobile.
starbeamrainbowlabs

28

In alternativa a bc, puoi usare awk all'interno del tuo script.

Per esempio:

echo "$IMG_WIDTH $IMG2_WIDTH" | awk '{printf "%.2f \n", $1/$2}'

In quanto sopra, "% .2f" dice alla funzione printf di restituire un numero in virgola mobile con due cifre dopo la cifra decimale. Ho usato echo per reindirizzare le variabili come campi poiché awk opera correttamente su di esse. "$ 1" e "$ 2" si riferiscono al primo e al secondo campo immessi in awk.

E puoi memorizzare il risultato come qualche altra variabile usando:

RESULT = `echo ...`

Eccellente! Grazie. Ciò è utile per l'ambiente incorporato in cui bc non è presente. Mi hai risparmiato del tempo per la compilazione incrociata.
entusiastico

19

È il momento perfetto per provare zsh, un (quasi) superset bash, con molte funzionalità aggiuntive tra cui la matematica in virgola mobile. Ecco come sarebbe il tuo esempio in zsh:

% IMG_WIDTH=1080
% IMG2_WIDTH=640
% result=$((IMG_WIDTH*1.0/IMG2_WIDTH))
% echo $result
1.6875

Questo post può aiutarti: bash - Vale la pena passare a zsh per un uso occasionale?


3
Sono un grande fan di zsh e lo uso da 4 anni, ma l'uso interattivo è una buona enfasi qui. Uno script che richiede zsh di solito non sarà molto portabile su un diverso set di macchine poiché di solito non è standard, purtroppo (per essere onesti, forse va bene; OP non ha detto esattamente come verrà utilizzato).
Brian Cline

17

Ebbene, prima del float c'era un tempo in cui veniva utilizzata la logica dei decimali fissi:

IMG_WIDTH=100
IMG2_WIDTH=3
RESULT=$((${IMG_WIDTH}00/$IMG2_WIDTH))
echo "${RESULT:0:-2}.${RESULT: -2}"
33.33

L'ultima riga è un bashim, se non usi bash, prova invece questo codice:

IMG_WIDTH=100
IMG2_WIDTH=3
INTEGER=$(($IMG_WIDTH/$IMG2_WIDTH))
DECIMAL=$(tail -c 3 <<< $((${IMG_WIDTH}00/$IMG2_WIDTH)))
RESULT=$INTEGER.$DECIMAL
echo $RESULT
33.33

La logica alla base del codice è: moltiplicare per 100 prima di dividere per ottenere 2 decimali.


5

Se hai trovato la variante che preferisci puoi anche racchiuderla in una funzione.

Qui sto avvolgendo un po 'di bashismo in una funzione div:

Una fodera:

function div { local _d=${3:-2}; local _n=0000000000; _n=${_n:0:$_d}; local _r=$(($1$_n/$2)); _r=${_r:0:-$_d}.${_r: -$_d}; echo $_r;}

O multi riga:

function div {
  local _d=${3:-2}
  local _n=0000000000
  _n=${_n:0:$_d}
  local _r=$(($1$_n/$2))
  _r=${_r:0:-$_d}.${_r: -$_d}
  echo $_r
}

Ora hai la funzione

div <dividend> <divisor> [<precision=2>]

e usalo come

> div 1 2
.50

> div 273 123 5
2.21951

> x=$(div 22 7)
> echo $x
3.14

AGGIORNAMENTO Ho aggiunto un piccolo script che fornisce le operazioni di base con numeri in virgola mobile per bash:

Utilizzo:

> add 1.2 3.45
4.65
> sub 1000 .007
999.993
> mul 1.1 7.07
7.7770
> div 10 3
3.
> div 10 3.000
3.333

E qui lo script:

#!/bin/bash
__op() {
        local z=00000000000000000000000000000000
        local a1=${1%.*}
        local x1=${1//./}
        local n1=$((${#x1}-${#a1}))
        local a2=${2%.*}
        local x2=${2//./}
        local n2=$((${#x2}-${#a2}))
        local n=$n1
        if (($n1 < $n2)); then
                local n=$n2
                x1=$x1${z:0:$(($n2-$n1))}
        fi
        if (($n1 > $n2)); then
                x2=$x2${z:0:$(($n1-$n2))}
        fi
        if [ "$3" == "/" ]; then
                x1=$x1${z:0:$n}
        fi
        local r=$(($x1"$3"$x2))
        local l=$((${#r}-$n))
        if [ "$3" == "*" ]; then
                l=$(($l-$n))
        fi
        echo ${r:0:$l}.${r:$l}
}
add() { __op $1 $2 + ;}
sub() { __op $1 $2 - ;}
mul() { __op $1 $2 "*" ;}
div() { __op $1 $2 / ;}

1
local _d=${3:-2}è più semplice
KamilCuk

4

Non è realmente in virgola mobile, ma se vuoi qualcosa che imposta più di un risultato in una chiamata di bc ...

source /dev/stdin <<<$(bc <<< '
d='$1'*3.1415926535897932384626433832795*2
print "d=",d,"\n"
a='$1'*'$1'*3.1415926535897932384626433832795
print "a=",a,"\n"
')

echo bc radius:$1 area:$a diameter:$d

calcola l'area e il diametro di un cerchio il cui raggio è dato in $ 1


4

Ci sono scenari in cui non è possibile utilizzare bc perché potrebbe semplicemente non essere presente, come in alcune versioni ridotte di busybox o sistemi embedded. In ogni caso limitare le dipendenze esterne è sempre una buona cosa, quindi puoi sempre aggiungere zeri al numero diviso per (numeratore), che equivale a moltiplicare per una potenza di 10 (dovresti scegliere una potenza di 10 secondo la precisione di cui hai bisogno), che renderà l'output della divisione un numero intero. Una volta ottenuto il numero intero, trattalo come una stringa e posiziona il punto decimale (spostandolo da destra a sinistra) un numero di volte pari alla potenza di dieci per cui hai moltiplicato il numeratore. Questo è un modo semplice per ottenere risultati float utilizzando solo numeri interi.


1
Anche Busybox ha Awk. Forse dovrebbe esserci una risposta Awk più evidente qui.
tripleee

4

Sebbene non sia possibile utilizzare la divisione in virgola mobile in Bash, è possibile utilizzare la divisione in virgola fissa. Tutto quello che devi fare è moltiplicare i tuoi numeri interi per una potenza di 10, quindi dividere la parte intera e utilizzare un'operazione modulo per ottenere la parte frazionaria. Arrotondamento secondo necessità.

#!/bin/bash

n=$1
d=$2

# because of rounding this should be 10^{i+1}
# where i is the number of decimal digits wanted
i=4
P=$((10**(i+1)))
Pn=$(($P / 10))
# here we 'fix' the decimal place, divide and round tward zero
t=$(($n * $P / $d + ($n < 0 ? -5 : 5)))
# then we print the number by dividing off the interger part and
# using the modulo operator (after removing the rounding digit) to get the factional part.
printf "%d.%0${i}d\n" $(($t / $P)) $(((t < 0 ? -t : t) / 10 % $Pn))

4

So che è vecchio, ma troppo allettante. quindi, la risposta è: non puoi ... ma puoi farlo. proviamo questo:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

$RATIO="$(( IMG_WIDTH / $IMG2_WIDTH )).$(( (IMG_WIDTH * 100 / IMG2_WIDTH) % 100 ))

in questo modo ottieni 2 cifre dopo il punto, troncate (chiamalo arrotondamento al minimo, haha) in puro bash (non è necessario avviare altri processi). ovviamente, se ti serve solo una cifra dopo il punto, moltiplica per 10 e fai il modulo 10.

cosa fa:

  • first $ ((...)) esegue la divisione intera;
  • second $ ((...)) esegue una divisione intera su qualcosa di 100 volte più grande, spostando essenzialmente le tue 2 cifre a sinistra del punto, quindi (%) ti ottiene solo quelle 2 cifre facendo modulo.

traccia bonus : la versione bc x 1000 ha richiesto 1,8 secondi sul mio laptop, mentre quella pura bash ha impiegato 0,016 secondi.


3

Per coloro che cercano di calcolare le percentuali con la risposta accettata, ma stanno perdendo precisione:

Se esegui questo:

echo "scale=2; (100/180) * 180" | bc

Ottieni 99.00solo, il che sta perdendo precisione.

Se lo esegui in questo modo:

echo "result = (100/180) * 180; scale=2; result / 1" | bc -l

Ora ottieni 99.99.

Perché stai ridimensionando solo al momento della stampa.

Fare riferimento qui


3

Come eseguire calcoli in virgola mobile in bash:

Invece di usare " here strings " ( <<<) con il bccomando, come fa uno degli esempi più votati , ecco il mio bcesempio in virgola mobile preferito , direttamente dalla EXAMPLESsezione delle bcpagine man (vedi man bcper le pagine di manuale).

Prima di iniziare, sappiamo che un'equazione per pi greco è: pi = 4*atan(1). a()di seguito è la bcfunzione matematica per atan().

  1. Ecco come memorizzare il risultato di un calcolo in virgola mobile in una variabile bash, in questo caso in una variabile chiamatapi . Notare che scale=10in questo caso imposta il numero di cifre decimali di precisione su 10. Eventuali cifre decimali dopo questa posizione vengono troncate .

     pi=$(echo "scale=10; 4*a(1)" | bc -l)
    
  2. Ora, per avere una singola riga di codice che stampa anche il valore di questa variabile, aggiungi semplicemente il echocomando alla fine come comando successivo, come segue. Nota il troncamento a 10 cifre decimali, come comandato:

     pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
     3.1415926532
    
  3. Infine, aggiungiamo alcuni arrotondamenti. Qui useremo la printffunzione per arrotondare a 4 cifre decimali. Nota che 3.14159...ora i round a 3.1416. Dato che stiamo arrotondando, non abbiamo più bisogno di scale=10troncare a 10 cifre decimali, quindi rimuoveremo semplicemente quella parte. Ecco la soluzione finale:

     pi=$(printf %.4f $(echo "4*a(1)" | bc -l)); echo $pi
     3.1416
    

Ecco un'altra fantastica applicazione e demo delle tecniche di cui sopra: misurazione e stampa del tempo di esecuzione.

(Vedi anche la mia altra risposta qui ).

Nota che dt_minviene arrotondato da 0.01666666666...a 0.017:

start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); echo "dt_sec = $dt_sec; dt_min = $dt_min"
dt_sec = 1; dt_min = 0.017

Relazionato:


1
risposta solida con esempi dettagliati e casi d'uso e round utilizzando printf
qos999

2

Usa calc. È l'esempio più semplice che ho trovato:

calc 1 + 1

 2

calc 1/10

 0.1

1

** Matematica in virgola mobile sicura per l'iniezione in bash / shell **

Nota: l'obiettivo di questa risposta è fornire idee per una soluzione sicura per l'iniezione per eseguire operazioni matematiche in bash (o altre shell). Naturalmente, lo stesso può essere utilizzato, con piccole modifiche per eseguire l'elaborazione avanzata delle corde, ecc.

La maggior parte delle soluzioni presentate, costruiscono al volo piccoli scriptlet, utilizzando dati esterni (variabili, file, riga di comando, variabili d'ambiente). L'input esterno può essere utilizzato per iniettare codice dannoso nel motore, molti dei quali

Di seguito è riportato un confronto sull'utilizzo dei vari linguaggi per eseguire calcoli matematici di base, dove il risultato è in virgola mobile. Calcola A + B * 0,1 (come virgola mobile).

Tutti i tentativi di soluzione evitano la creazione di scriptlet dinamici, che sono estremamente difficili da mantenere, utilizzano invece programmi statici e passano parametri nella variabile designata. Gestiranno in modo sicuro i parametri con caratteri speciali, riducendo la possibilità di iniezione di codice. L'eccezione è "BC" che non fornisce funzionalità di input / output

L'eccezione è "bc", che non fornisce alcun input / output, tutti i dati arrivano tramite programmi in stdin e tutto l'output va a stdout. Tutti i calcoli vengono eseguiti in una sandbox, che non consente effetti collaterali (apertura di file, ecc.). In teoria, iniezione sicura per progettazione!

A=5.2
B=4.3

# Awk: Map variable into awk
# Exit 0 (or just exit) for success, non-zero for error.
#
awk -v A="$A" -v B="$B" 'BEGIN { print A + B * 0.1 ; exit 0}'

# Perl
perl -e '($A,$B) = @ARGV ; print $A + $B * 0.1' "$A" "$B"

# Python 2
python -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print a+b*0.1' "$A" "$B"

# Python 3
python3 -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print(a+b*0.1)' "$A" "$B"

# BC
bc <<< "scale=1 ; $A + $B * 0.1"

per python3 con argomenti arbitrari: python3 -c 'import sys ; *a, = map(float, sys.argv[1:]) ; print(a[0] + a[1]*0.1 + a[2])' "$A" "$B""4200.0" ==> 4205.63
WiringHarness

0

ecco il comando awk: -F = separatore di campo == +

echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.