Paréntesis en aritmética expr: 3 * (2 + 1)

60

expr no parece gustarle el paréntesis (usado en matemáticas para dar prioridad explícita al operador):

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

¿Cómo expresar la prioridad del operador en bash?

Nicolas Raoul
fuente

Respuestas:

40

Otra forma de usar letbash builtin:

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

Nota

Como señaló @ Stéphane Chazelas , bashdebe usar ((...))para hacer aritmética sobre expro letpara legibilidad.

Para la portabilidad, use $((...))como @Bernhard answer .

Cuonglm
fuente
1
+1 ¡Aún más legible! Publiqué mi pregunta + respuesta pensando que sería útil para mis compañeros usuarios de Linux, pero ahora estoy obteniendo muchos beneficios de las otras respuestas :-)
Nicolas Raoul
3
No hay razón para estar usando let. No es más estándar o portátil que (( a = 3 * (2 + 1) ))(ambos provienen kshy solo están disponibles en ksh, bash y zsh) y es menos legible o fácil de cotizar. Solía a=$((3 * (2 + 1)))ser portátil.
Stéphane Chazelas
2
No digo que esté mal, solo digo que no debería usarse, ya que hay mejores alternativas (una para la legibilidad ((a = 3 * (2 + 1) )), otra para la portabilidad a=$((3 * (2 + 1)))), por lo que no es una nota en su contra o en su respuesta, sino en contra de que sea la respuesta seleccionada y máximo goleador.
Stéphane Chazelas
@ StéphaneChazelas: ¡Actualicé mi respuesta!
Cuonglm
Siempre he usado a=1 $[a+2]o a=1 b=2 $[a+b]. ¿Es su razón para evitar esa sintaxis?
Gordon
74

Puede usar la expansión aritmética en su lugar.

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

En mi opinión personal, esto se ve un poco mejor que usar expr.

De man bash

Expansión aritmética La expansión aritmética permite la evaluación de una expresión aritmética y la sustitución del resultado. El formato para la expansión aritmética es:

         $((expression))

La expresión se trata como si estuviera entre comillas dobles, pero una comilla doble entre paréntesis no se trata especialmente. Todos los tokens en la expresión se someten a expansión de parámetros, expansión de cadena, sustitución de comandos y eliminación de comillas. Las expansiones aritméticas pueden estar anidadas.

La evaluación se realiza de acuerdo con las reglas enumeradas a continuación en EVALUACIÓN ARITMÉTICA. Si la expresión no es válida, bash imprime un mensaje que indica un error y no se produce ninguna sustitución.

Bernhard
fuente
1
Además de la legibilidad, tampoco requiere bifurcar un proceso adicional para hacer la aritmética; es manejado por el propio shell.
chepner
Tenga en cuenta que en shells POSIX, está sujeto a la división de palabras, por lo que es un buen hábito citarlo en contextos de lista.
Stéphane Chazelas
Cuando intento esto en el shell bash obtengo 'Nombre de variable ilegal. "
lordhog
40

No hay razón para usar expraritmética en conchas modernas.

POSIX define el $((...))operador de expansión. Por lo tanto, puede usar eso en todos los shells compatibles con POSIX (el shde todos los gustos modernos de Unix, dash, bash, yash, mksh, zsh, posh, ksh ...).

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

kshTambién introdujo un valor letincorporado que se pasa el mismo tipo de expresión aritmética, no se expande en algo, pero devuelve un estado de salida en función de si la expresión se resuelve en 0 o no, como en expr:

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

Sin embargo, como la cita lo hace incómodo y poco legible (no en la misma medida que, exprpor supuesto), kshtambién introdujo una ((...))forma alternativa:

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

que es mucho más legible y debería usarse en su lugar.

lety ((...))solo están disponibles en ksh, zshy bash. Se $((...))debe preferir la sintaxis si se necesita la portabilidad a otros shells, exprsolo se necesita para los shells pre-POSIX similares a Bourne (típicamente el shell Bourne o las primeras versiones del shell Almquist).

En el frente no Bourne, hay algunos shells con operador aritmético incorporado:

  • csh/ tcsh(en realidad, el primer shell de Unix con evaluación aritmética incorporada):

    @ a = 3 * (2 + 1)
  • akanga(basado en rc)

    a = $:'3 * (2 + 1)'
  • Como nota histórica, la versión original del shell Almquist, publicada en Usenet en 1989, tenía una versión exprincorporada (en realidad se fusionó con test), pero se eliminó más tarde.

Stéphane Chazelas
fuente
Cada día aprendo algo nuevo de ti, Stéphane. ¡Aprecio mucho su conocimiento de shell POSIX!
MattBianco
¿Qué tal : $((a = a*2))?
Arthur2e5
¿Qué pasa si tengo un punto flotante? Mi expresión es a = $ ((-14 + 0.2 * (1 + 2 + 3))). El token de error es ".2 * (1 + 2 + 3)"
Blaise
@Blaise, entonces necesitarías un shell que admita puntos flotantes $((...))como zsh, ksh93 o yash.
Stéphane Chazelas
16

expres un comando externo, no es una sintaxis especial de shell. Por lo tanto, si desea exprver caracteres especiales de shell, debe protegerlos del análisis de shell al citarlos. Además, exprnecesita que cada número y operador se pasen como un parámetro separado. Así:

expr 3 \* \( 2 + 1 \)

A menos que esté trabajando en un sistema antiguo de Unix de los años 1970 u 1980, hay muy pocas razones para usarlo expr. En los viejos tiempos, los shells no tenían una forma integrada de realizar operaciones aritméticas, y en su lugar había que llamar a la exprutilidad. Todos los shells POSIX tienen aritmética incorporada a través de la sintaxis de expansión aritmética .

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

La construcción se $((…))expande al resultado de la expresión aritmética (escrita en decimal). Bash, como la mayoría de los shells, solo admite aritmética de enteros módulo 2 64 (o módulo 2 32 para versiones anteriores de bash y algunos otros shells en máquinas de 32 bits).

Bash ofrece una sintaxis de conveniencia adicional cuando desea realizar asignaciones o probar si una expresión es 0 pero no le importa el resultado. Esta construcción también existe en ksh y zsh pero no en sh simple.

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

Además de la aritmética de enteros, exprofrece algunas funciones de manipulación de cadenas. Estos también están incluidos en las características de los shells POSIX, excepto uno: expr STRING : REGEXPprueba si la cadena coincide con la expresión regular especificada. Un shell POSIX no puede hacer esto sin herramientas externas, pero bash puede con [[ STRING =~ REGEXP ]](con una sintaxis regexp diferenteexpres una herramienta clásica y usa BRE, bash usa ERE).

A menos que esté manteniendo scripts que se ejecutan en sistemas de 20 años, no necesita saber que expralguna vez existió. Usa aritmética de conchas.

Gilles 'SO- deja de ser malvado'
fuente
expr foo : '\(.\)'también hace extracción de texto. bash's BASH_REMATCHlogra algo similar. También hace una comparación de cadenas, que POSIX [no hace (aunque uno podría imaginar formas de usar sortpara eso).
Stéphane Chazelas
subrayar como marcador de posición de sintaxis --- usted un Schemer, @Giles? :]
RubyTuesdayDONO
1
@RubyTuesdayDONO No utilicé un guión bajo aquí. ¿Estás leyendo mal ELLIPSIS HORIZONTAL U + 2026? Si es así, intente usar una fuente más grande.
Gilles 'SO- deja de ser malvado'
@Giles: está bien, sí, solo parece un guión bajo debido al tamaño de mi fuente. para mí, "Schemer" es un complemento, y no es como si la elipsis versus el guión bajo cambiara el significado de todos modos ... no es necesario volverse sarcástico: /
RubyTuesdayDONO
12

Use paréntesis con comillas:

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

Las comillas evitan que bash interprete el paréntesis como sintaxis bash.

Nicolas Raoul
fuente
2
Lo que Nicolas ilustra pero no explica es que los tokens en la exprlínea de comando deben estar separados por espacios; entonces; Por ejemplo, expr 3 "*" "(2" "+" "1)" no funcionará . (Además, por cierto, probablemente no necesites citar el +.)
G-Man dice 'Reinstate Monica' el
Los paréntesis no son palabras clave como whiley [[, son sintaxis. Si fueran palabras clave, no se interpretarían como tales en los argumentos de comando. Necesita comillas para que bash no las analice, sino que vea un literal de cadena.
Gilles 'SO- deja de ser malvado'
1

Si tienes bc ..

echo '3 * (2 + 1)'|bc 
9                                                                    
robar
fuente