Klammern in Ausdrucksarithmetik: 3 * (2 + 1)

60

expr scheint Klammern nicht zu mögen (wird in der Mathematik verwendet, um die Operatorpriorität zu explizieren):

expr 3 * (2 + 1)
bash: syntax error near unexpected token `('

Wie drücke ich die Operatorpriorität in bash aus?

Nicolas Raoul
quelle

Antworten:

40

Eine andere Möglichkeit, letbash builtin zu verwenden:

$ let a="3 * (2 + 1)"
$ printf '%s\n' "$a"
9

Hinweis

Wie @ Stéphane Chazelas betonte , bashsollten Sie in verwenden ((...)), um Arithmetik über exproder letfür die Lesbarkeit zu tun .

Für Portabilität, verwenden $((...))wie @Bernhard Antwort .

cuonglm
quelle
1
+1 Noch besser lesbar! Ich habe meine Frage + Antwort gepostet, weil ich dachte, es wäre hilfreich für meine Linux-Kollegen, aber jetzt profitiere ich sehr von den anderen Antworten :-)
Nicolas Raoul
3
Es gibt keinen Grund, etwas zu benutzen let. Es ist nicht standardmäßiger oder portabler als (( a = 3 * (2 + 1) ))(beide kommen von kshund sind nur in ksh, bash und zsh verfügbar) und es ist weniger lesbar oder leicht zu zitieren. Gebrauch a=$((3 * (2 + 1))), beweglich zu sein.
Stéphane Chazelas
2
Ich sage nicht, dass es falsch ist, ich sage nur, dass es nicht verwendet werden sollte, da es bessere Alternativen gibt (eine für Lesbarkeit ((a = 3 * (2 + 1) )), eine für Portabilität a=$((3 * (2 + 1)))), es ist also keine Notiz gegen Sie oder Ihre Antwort, sondern dagegen, dass es die ausgewählte Antwort ist und Torschützenkönig.
Stéphane Chazelas
@ StéphaneChazelas: Aktualisiert meine Antwort!
Cuonglm
Ich habe immer benutzt a=1 $[a+2]oder a=1 b=2 $[a+b]. Ist ihr Grund, diese Syntax zu vermeiden?
Gordon
74

Sie können stattdessen die arithmetische Erweiterung verwenden.

echo "$(( 3 * ( 2 + 1 ) ))"
9

Meiner persönlichen Meinung nach sieht das ein bisschen besser aus als mit expr.

Von man bash

Arithmetische Erweiterung Die arithmetische Erweiterung ermöglicht die Auswertung eines arithmetischen Ausdrucks und die Substitution des Ergebnisses. Das Format für die arithmetische Erweiterung lautet:

         $((expression))

Der Ausdruck wird so behandelt, als befände er sich in doppelten Anführungszeichen, ein doppeltes Anführungszeichen in Klammern wird jedoch nicht speziell behandelt. Alle Token im Ausdruck werden einer Parametererweiterung, einer Zeichenfolgenerweiterung, einer Befehlssubstitution und einer Entfernung von Anführungszeichen unterzogen. Arithmetische Erweiterungen können verschachtelt sein.

Die Auswertung erfolgt nach den nachfolgend unter ARITHMETISCHE BEWERTUNG aufgeführten Regeln. Wenn der Ausdruck ungültig ist, gibt die Bash eine Meldung aus, die auf einen Fehler hinweist, und es findet keine Ersetzung statt.

Bernhard
quelle
1
Abgesehen von der Lesbarkeit ist es auch nicht erforderlich, einen zusätzlichen Prozess zu forken, um die Arithmetik auszuführen. Es wird von der Shell selbst gehandhabt.
Chepner
Beachten Sie, dass es in POSIX-Shells zu einer Wortteilung kommt, weshalb es eine gute Angewohnheit ist, es in Listenkontexten zu zitieren.
Stéphane Chazelas
Wenn ich dies an der Bash-Shell versuche, erhalte ich den
ungültigen
40

Es gibt keinen Grund, exprin modernen Schalen für das Rechnen zu verwenden.

POSIX definiert den $((...))Expansionsoperator. Sie können das also in allen POSIX-kompatiblen Shells verwenden (die shaller modernen Unix-Likes, Dash, Bash, Yash, Mksh, Zsh, Posh, Ksh ...).

a=$(( 3 * (2 + 1) ))
a=$((3*(2+1)))

kshAußerdem wurde ein letBuiltin eingeführt, das denselben arithmetischen Ausdruck übergibt, nicht zu etwas erweitert, sondern einen Exit-Status zurückgibt, der davon abhängt, ob der Ausdruck in 0 aufgelöst wird oder nicht, wie in expr:

if let 'a = 3 * (2 + 1)'; then
  echo "$a is non-zero"
fi

Da das Zitat jedoch umständlich und nicht sehr leserlich ist (nicht im gleichen Maße wie exprnatürlich), wurde kshauch eine ((...))alternative Form eingeführt:

if (( a = 3 * (2 + 1) )) && (( 3 > 1 )); then
  echo "$a is non-zero and 3 > 1"
fi
((a+=2))

Das ist viel besser lesbar und sollte stattdessen verwendet werden.

letund ((...))sind nur in ksh, zshund bash. Die $((...))Syntax sollte bevorzugt werden, wenn die Portierbarkeit auf andere Shells erforderlich ist. Dies exprist nur für Bourne-ähnliche Shells vor POSIX erforderlich (normalerweise die Bourne-Shell oder frühe Versionen der Almquist-Shell).

An der Nicht-Bourne-Front gibt es einige Shells mit integriertem arithmetischen Operator:

  • csh/ tcsh(eigentlich die erste Unix-Shell mit integrierter arithmetischer Auswertung):

    @ a = 3 * (2 + 1)
  • akanga(basierend auf rc)

    a = $:'3 * (2 + 1)'
  • als geschichtsnotiz hatte die originalversion der Almquist-shell, wie sie 1989 im Usenet veröffentlicht wurde, einen expreingebauten (tatsächlich zusammengeführten test), der jedoch später entfernt wurde.

Stéphane Chazelas
quelle
Ich lerne jeden Tag etwas Neues von dir, Stéphane. Ich schätze Ihre POSIX-Shell-Kenntnisse sehr!
MattBianco
Wie wäre es : $((a = a*2))?
Arthur2e5
Was ist, wenn ich einen Gleitkomma habe? Mein Ausdruck ist a = $ ((-14 + 0,2 * (1 + 2 + 3))). Das Fehlertoken lautet ".2 * (1 + 2 + 3)"
Blaise,
@Blaise, dann brauchst du eine Shell, die Gleitkommazahlen $((...))wie zsh, ksh93 oder yash unterstützt.
Stéphane Chazelas
16

exprist ein externer Befehl, keine spezielle Shell-Syntax. Wenn Sie exprShell-Sonderzeichen sehen möchten , müssen Sie sie daher vor Shell-Analyse schützen, indem Sie sie in Anführungszeichen setzen. Darüber hinaus exprmuss jede Nummer und jeder Operator als separater Parameter übergeben werden. Somit:

expr 3 \* \( 2 + 1 \)

Sofern Sie nicht an einem antiken Unix-System aus den 1970er oder 1980er Jahren arbeiten, gibt es nur sehr wenige Gründe, es zu verwenden expr. Früher hatten Shells keine eingebaute Methode zum Rechnen, und Sie mussten exprstattdessen das Dienstprogramm aufrufen . Alle POSIX-Shells verfügen über eine integrierte Arithmetik über die arithmetische Erweiterungssyntax .

echo "$((3 * (2 + 1)))"

Das Konstrukt $((…))expandiert zum Ergebnis des arithmetischen Ausdrucks (in Dezimalschrift geschrieben). Bash unterstützt wie die meisten Shells nur Ganzzahl-Arithmetikmodule 2 64 (oder Modulo 2 32 für ältere Versionen von Bash und einige andere Shells auf 32-Bit-Computern).

Bash bietet eine zusätzliche Komfortsyntax, wenn Sie Zuweisungen ausführen oder testen möchten, ob ein Ausdruck 0 ist, aber das Ergebnis nicht berücksichtigt. Dieses Konstrukt existiert auch in ksh und zsh, aber nicht in plain sh.

((x = 3 * (2+1)))
echo "$x"
if ((x > 3)); then 

Bietet zusätzlich zur Ganzzahlarithmetik expreinige Funktionen zur Manipulation von Zeichenfolgen. Auch diese werden von den Funktionen der POSIX-Shells zusammengefasst, mit einer Ausnahme: expr STRING : REGEXPPrüft, ob die Zeichenfolge mit dem angegebenen regulären Ausdruck übereinstimmt. Eine POSIX-Shell kann dies nicht ohne externe Tools tun, aber Bash kann mit [[ STRING =~ REGEXP ]](mit einer anderen Regexp-Syntaxexprist ein klassisches Tool und verwendet BRE, Bash verwendet ERE).

Sofern Sie keine Skripte verwalten, die auf 20 Jahre alten Systemen ausgeführt werden, müssen Sie nicht wissen, dass es diese exprjemals gab. Verwenden Sie Shell-Arithmetik.

Gilles 'SO - hör auf böse zu sein'
quelle
expr foo : '\(.\)'Führt auch die Textextraktion durch. bash's BASH_REMATCHschafft sowas ähnliches. Es führt auch einen Zeichenfolgenvergleich durch, den POSIX [nicht durchführt (obwohl man sich Möglichkeiten vorstellen könnte, dies zu tun sort).
Stéphane Chazelas
Unterstreichen Sie als Syntax-Platzhalter --- Sie ein Schemer, @Giles? :]
RubyTuesdayDONO
1
@RubyTuesdayDONO Ich habe hier keinen Unterstrich verwendet. Lesen Sie U + 2026 HORIZONTAL ELLIPSIS falsch? Verwenden Sie in diesem Fall eine größere Schrift.
Gilles 'SO- hör auf böse zu sein'
@Giles - okay, ja, aufgrund meiner Schriftgröße sieht es nur nach einem Unterstrich aus. für mich „ Schemer“ ist eine Ergänzung, und es ist nicht wie Ellipsen gegen Strich ändert die Bedeutung sowieso ... keine Notwendigkeit , snarky um darüber hinwegzukommen: /
RubyTuesdayDONO
12

Verwenden Sie Klammern mit Anführungszeichen:

expr 3 '*' '(' 2 '+' 1 ')'
9

Die Anführungszeichen verhindern, dass Bash die Klammern als Bash-Syntax interpretiert.

Nicolas Raoul
quelle
2
Was Nicolas illustriert, aber nicht erklärt, ist, dass die Token in der exprBefehlszeile durch Leerzeichen getrennt sein müssen. damit; zum Beispiel expr 3 "*" "(2" "+" "1)" wird nicht funktionieren . (Übrigens müssen Sie das wahrscheinlich nicht zitieren +.)
G-Man
Die Klammern sind keine Schlüsselwörter wie whileund [[, sie sind Syntax. Wären sie Schlüsselwörter, würden sie in Befehlsargumenten nicht als solche interpretiert. Sie benötigen Anführungszeichen, damit bash sie nicht analysiert, sondern stattdessen ein Zeichenfolgenliteral anzeigt.
Gilles 'SO - hör auf böse zu sein'
1

Wenn Sie bc haben ..

echo '3 * (2 + 1)'|bc 
9                                                                    
rauben
quelle