Jak mogę dodawać liczby w skrypcie Bash?

566

Mam ten skrypt Bash i miałem problem z wierszem 16. Jak mogę pobrać poprzedni wynik z wiersza 15 i dodać go do zmiennej w wierszu 16?

#!/bin/bash

num=0
metab=0

for ((i=1; i<=2; i++)); do
    for j in `ls output-$i-*`; do
        echo "$j"

        metab=$(cat $j|grep EndBuffer|awk '{sum+=$2} END { print sum/120}') (line15)
        num= $num + $metab   (line16)
    done
    echo "$num"
 done
Nacięcie
źródło
2
Myślę, że wydrukujesz wartość zerową nawet przy użyciu następującej odpowiedzi. Istnieje sztuczka, np. Podstawienie procesu, aby doprowadzić końcową wartość „liczby wewnętrznej” do „liczby zewnętrznej”.
Scott Chu,

Odpowiedzi:

974

Dla liczb całkowitych :

  • Użyj rozszerzenia arytmetycznego :$((EXPR))

    num=$((num1 + num2))
    num=$(($num1 + $num2))       # Also works
    num=$((num1 + 2 + 3))        # ...
    num=$[num1+num2]             # Old, deprecated arithmetic expression syntax
  • Korzystanie z zewnętrznego exprnarzędzia. Pamiętaj, że jest to potrzebne tylko w przypadku naprawdę starych systemów.

    num=`expr $num1 + $num2`     # Whitespace for expr is important

Dla zmiennoprzecinkowego :

Bash nie obsługuje tego bezpośrednio, ale jest kilka zewnętrznych narzędzi, których możesz użyć:

num=$(awk "BEGIN {print $num1+$num2; exit}")
num=$(python -c "print $num1+$num2")
num=$(perl -e "print $num1+$num2")
num=$(echo $num1 + $num2 | bc)   # Whitespace for echo is important

Możesz także użyć notacji naukowej (na przykład 2.5e+2).


Typowe pułapki :

  • Podczas ustawiania zmiennej nie możesz mieć białych znaków po żadnej stronie =, w przeciwnym razie wymusi ona na powłoce interpretację pierwszego słowa jako nazwy aplikacji do uruchomienia (na przykład num=lub num)

    num= 1 num =2

  • bci exproczekuj każdej liczby i operatora jako osobnego argumentu, więc spacja jest ważna. Nie mogą przetwarzać argumentów takich jak 3+ +4.

    num=`expr $num1+ $num2`

Karoly Horvath
źródło
1
W pierwszej odpowiedzi wypisuje mi wyrażenie: argument nienumeryczny Druga nie działa ..
Nick
1
@Nick: Czy próbowałeś tego, gdy zmienne o nazwie num1i num2istnieniu miały wartości całkowite?
sorpigal
1
Cis istnieją, ale jedna zmienna jest podwójna .. czy to problem?
Nick
2
Tak, to jest problem :) Uwaga: $((..))ocena arytmetyczna jest wykonywana w trybie bash. exprjest wykonywany jako osobny proces, więc będzie o wiele wolniejszy. użyj tego drugiego w systemach, w których ocena arytmetyczna nie jest obsługiwana (sh! = bash)
Karoly Horvath
4
Ponieważ $((…))jest standaryzowany przez POSIX, powinno to być coraz rzadsze, co exprjest konieczne.
chepner
115

Użyj $(( ))rozszerzenia arytmetycznego.

num=$(( $num + $metab ))

Aby uzyskać więcej informacji, zobacz Rozdział 13. Rozbudowa arytmetyczna .

Steve Prentice
źródło
2
Dziwne. Nie działa tutaj, gdy próbuję obliczyć 191.003 + 190.
Sigur,
2
Nie działa dla liczb z przecinkiem dziesiętnym.
fivedogit,
30

Jest na to tysiąc sposobów. Oto jeden z nich dc(odwrócony polski kalkulator biurkowy, który obsługuje arytmetykę o nieograniczonej precyzji):

dc <<<"$num1 $num2 + p"

Ale jeśli to dla ciebie zbyt błaha (lub ważne jest przenośność), możesz powiedzieć

echo $num1 $num2 + p | dc

Ale może jesteś jedną z tych osób, które uważają RPN za niegrzeczne i dziwne; nie martw się! bcjest tu dla ciebie:

bc <<< "$num1 + $num2"
echo $num1 + $num2 | bc

To powiedziawszy, istnieje kilka niepowiązanych ulepszeń w skrypcie:

#!/bin/bash

num=0
metab=0

for ((i=1; i<=2; i++)); do
    for j in output-$i-* ; do # 'for' can glob directly, no need to ls
            echo "$j"

             # 'grep' can read files, no need to use 'cat'
            metab=$(grep EndBuffer "$j" | awk '{sum+=$2} END { print sum/120}')
            num=$(( $num + $metab ))
    done
    echo "$num"
done

Jak opisano w Bash FAQ 022 , Bash natywnie nie obsługuje liczb zmiennoprzecinkowych. Jeśli chcesz zsumować liczby zmiennoprzecinkowe, wymagane jest użycie zewnętrznego narzędzia (takiego jak bclub dc).

W takim przypadku rozwiązaniem byłoby

num=$(dc <<<"$num $metab + p")

Aby dodać kumulować możliwie zmiennoprzecinkowe liczby do num.

sorpigal
źródło
1
@Sorpigal Thankssss dużo .. Czy mogę prosić o coś innego .. jak mogę wydrukować na końcu nie numer, ale numer / 10?
Nick
23

W bash

 num=5
 x=6
 (( num += x ))
 echo $num   # ==> 11

Zauważ, że bash obsługuje tylko arytmetykę całkowitoliczbową, więc jeśli twoje polecenie awk zwróci ułamek, to będziesz chciał przeprojektować: oto twój kod przepisany, by zrobić całą matematykę w awk.

num=0
for ((i=1; i<=2; i++)); do      
    for j in output-$i-*; do
        echo "$j"
        num=$(
           awk -v n="$num" '
               /EndBuffer/ {sum += $2}
               END {print n + (sum/120)}
           ' "$j"
        )
    done
    echo "$num"
done
Glenn Jackman
źródło
20

Zawsze zapominam o składni, więc wchodzę na google, ale nigdy nie znajduję tej, którą znam: P. To jest dla mnie najczystsze i bardziej zgodne z tym, czego oczekiwałbym w innych językach.

i=0
((i++))

echo $i;
ssshake
źródło
18
 #!/bin/bash
read X
read Y
echo "$(($X+$Y))"
Amarjeet Singh
źródło
1
Działa dobrze w systemie macOS.
Tmx 17.01.19
15

Bardzo podoba mi się również ta metoda, mniej bałaganu:

count=$[count+1]
unixball
źródło
1
Nawiasem mówiąc, dlaczego to działa? Jak to nazywamy? Nie mogę znaleźć dokumentów na ich temat.
skyline75489
4
Mniej bałaganu, ale przestarzałe.
Camille Goudeseune
7

Kolejny przenośny POSIXzgodny sposób bash, który można zdefiniować jako funkcję .bashrcdla wszystkich operatorów arytmetycznych dla wygody.

addNumbers () {
    local IFS='+'
    printf "%s\n" "$(( $* ))"
}

i po prostu wywołaj to w wierszu poleceń jako,

addNumbers 1 2 3 4 5 100
115

Chodzi o to, aby użyć Separatora pól wejściowych (IFS) , specjalnej zmiennej bashużywanej do dzielenia słów po rozwinięciu i dzielenia linii na słowa. Funkcja zmienia lokalnie wartość, aby użyć znaku dzielącego słowa jako operatora sumy +.

Pamiętaj, że IFSzmiany są zmieniane lokalnie i NIE wpływają na domyślne IFSzachowanie poza zakresem funkcji. Fragment man bashstrony

Powłoka traktuje każdy znak IFS jako ogranicznik i dzieli wyniki pozostałych rozszerzeń na słowa na te znaki. Jeśli IFS jest rozbrojony lub jego wartość jest dokładnie domyślna, wówczas sekwencje, oraz na początku i na końcu wyników poprzednich rozszerzeń są ignorowane, a dowolna sekwencja znaków IFS nie na początku ani na końcu służy do rozgraniczenia słowa.

"$(( $* ))"Przedstawia listę argumentów przekazanych do podziału przez +a później wartość suma jest odtwarzany za pomocą printffunkcji. Funkcję tę można rozszerzyć, aby dodać także zakres innych operacji arytmetycznych.

Inian
źródło
2
#!/bin/bash

num=0
metab=0

for ((i=1; i<=2; i++)); do      
    for j in `ls output-$i-*`; do
        echo "$j"

        metab=$(cat $j|grep EndBuffer|awk '{sum+=$2} END { print sum/120}') (line15)
        let num=num+metab (line 16)
    done
    echo "$num"
done
Milen John Thomas
źródło