¿Cómo incrementar una variable en bash?

609

He intentado incrementar una variable numérica usando ambos var=$var+1y var=($var+1)sin éxito. La variable es un número, aunque bash parece estar leyéndolo como una cadena.

Bash versión 4.2.45 (1) -release (x86_64-pc-linux-gnu) en Ubuntu 13.10.

usuario221744
fuente

Respuestas:

948

Hay más de una forma de incrementar una variable en bash, pero lo que intentó no es correcto.

Puede usar, por ejemplo, la expansión aritmética :

var=$((var+1))
((var=var+1))
((var+=1))
((var++))

O puedes usar let:

let "var=var+1"
let "var+=1"
let "var++"

Ver también: http://tldp.org/LDP/abs/html/dblparens.html .

Radu Rădeanu
fuente
31
o ((++var))o ((var=var+1))o ((var+=1)).
gniourf_gniourf
66
Curiosamente, var=0; ((var++))devuelve un código de error mientras var=0; ((var++)); ((var++))que no. ¿Alguna idea de por qué?
phunehehe
15
@phunehehe Mira help '(('. La última línea dice:Returns 1 if EXPRESSION evaluates to 0; returns 0 otherwise.
Radu Rădeanu
2
Sospecho que la evaluación de cero 1es la razón por la cual la sugerencia de @ gniourf_gniourf incluye ((++var))pero no ((var++)).
DreadPirateShawn
44
¿Es seguro de usar let var++, sin las comillas?
wjandrea
161
var=$((var + 1))

La aritmética en bash usa la $((...))sintaxis.

Paul Tanzini
fuente
99
Mucho mejor que la respuesta aceptada. En solo un 10% de espacio, se las arregló para proporcionar suficientes ejemplos (uno es suficiente, nueve es excesivo hasta el punto en que se está presumiendo), y nos proporcionó suficiente información para saber que esa ((...))es la clave para usar la aritmética en fiesta No me di cuenta de que solo mirando la respuesta aceptada, pensé que había un conjunto extraño de reglas sobre el orden de las operaciones o algo que condujera a todos los paréntesis en la respuesta aceptada.
ArtOfWarfare
82

Análisis de rendimiento de varias opciones.

Gracias a la respuesta de Radu Rădeanu que proporciona las siguientes formas de incrementar una variable en bash:

var=$((var+1))
((var=var+1))
((var+=1))
((var++))
let "var=var+1"
let "var+=1" 
let "var++"

También hay otras formas. Por ejemplo, mira las otras respuestas sobre esta pregunta.

let var++
var=$((var++))
((++var))
{
    declare -i var
    var=var+1
    var+=1
}
{
    i=0
    i=$(expr $i + 1)
}

Tener tantas opciones lleva a estas dos preguntas:

  1. ¿Hay una diferencia de rendimiento entre ellos?
  2. Si es así, ¿cuál funciona mejor?

Código de prueba de rendimiento incremental:

#!/bin/bash

# To focus exclusively on the performance of each type of increment
# statement, we should exclude bash performing while loops from the
# performance measure. So, let's time individual scripts that
# increment $i in their own unique way.

# Declare i as an integer for tests 12 and 13.
echo > t12 'declare -i i; i=i+1'
echo > t13 'declare -i i; i+=1'
# Set i for test 14.
echo > t14 'i=0; i=$(expr $i + 1)'

x=100000
while ((x--)); do
    echo >> t0 'i=$((i+1))'
    echo >> t1 'i=$((i++))'
    echo >> t2 '((i=i+1))'
    echo >> t3 '((i+=1))'
    echo >> t4 '((i++))'
    echo >> t5 '((++i))'
    echo >> t6 'let "i=i+1"'
    echo >> t7 'let "i+=1"'
    echo >> t8 'let "i++"'
    echo >> t9 'let i=i+1'
    echo >> t10 'let i+=1'
    echo >> t11 'let i++'
    echo >> t12 'i=i+1'
    echo >> t13 'i+=1'
    echo >> t14 'i=$(expr $i + 1)'
done

for script in t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14; do
    line1="$(head -1 "$script")"
    printf "%-24s" "$line1"
    { time bash "$script"; } |& grep user
    # Since stderr is being piped to grep above, this will confirm
    # there are no errors from running the command:
    eval "$line1"
    rm "$script"
done

Resultados:

i=$((i+1))              user    0m0.992s
i=$((i++))              user    0m0.964s
((i=i+1))               user    0m0.760s
((i+=1))                user    0m0.700s
((i++))                 user    0m0.644s
((++i))                 user    0m0.556s
let "i=i+1"             user    0m1.116s
let "i+=1"              user    0m1.100s
let "i++"               user    0m1.008s
let i=i+1               user    0m0.952s
let i+=1                user    0m1.040s
let i++                 user    0m0.820s
declare -i i; i=i+1     user    0m0.528s
declare -i i; i+=1      user    0m0.492s
i=0; i=$(expr $i + 1)   user    0m5.464s

Conclusión:

Parece que bash es más rápido i+=1cuando $ise declara como un entero. letLas declaraciones parecen particularmente lentas, y expres, con mucho, la más lenta porque no está integrada.

wjandrea
fuente
Aparentemente, la velocidad se correlaciona con la longitud del comando. Me pregunto si los comandos llaman a las mismas funciones.
MatthewRock
18

También hay esto:

var=`expr $var + 1`

Tome nota de los espacios y también ' no es '

Si bien las respuestas de Radu y los comentarios son exhaustivos y muy útiles, son específicos de bash. Sé que preguntaste específicamente sobre bash, pero pensé en hablar desde que encontré esta pregunta cuando estaba buscando hacer lo mismo usando sh en busybox en uCLinux. Este portátil más allá de bash.

tphelican
fuente
1
También puedes usari=$((i+1))
wjandrea
Si la sustitución del proceso $(...)está disponible en este shell, recomendaría usarlo en su lugar.
Radon Rosborough
7

Falta un método en todas las respuestas: bc

$ VAR=7    
$ bc <<< "$VAR+2"
9
$ echo $VAR
7
$ VAR=$( bc <<< "$VAR+1" )
$ echo $VAR
8

bcestá especificado por el estándar POSIX , por lo que debe estar presente en todas las versiones de Ubuntu y sistemas compatibles con POSIX. La <<<redirección podría modificarse echo "$VAR" | bcpara la portabilidad, pero dado que la pregunta se refiere a bash: está bien usarla <<<.

Sergiy Kolodyazhnyy
fuente
6

El código de retorno 1tema está presente para todas las variantes (por defecto let, (()), etc.). Esto a menudo causa problemas, por ejemplo, en los scripts que usan set -o errexit. Esto es lo que estoy usando para evitar que el código de error 1de las expresiones matemáticas evalúen 0;

math() { (( "$@" )) || true; }

math a = 10, b = 10
math a++, b+=2
math c = a + b
math mod = c % 20
echo $a $b $c $mod
#11 12 23 3
Juve
fuente
0

Esta tiene que ser la peor manera de lograr una tarea tan simple, pero supongo que solo quería documentarlo por diversión (completamente opuesto al código golf).

$ var=0
$ echo $var
0
$ var="$(python -c 'print('$var'+1)')"
$ echo $var
1

o

$ var="$(printf '%s\n' $var'+1' | bc)"
$ echo $var
1

En serio, use una de las otras opciones mucho mejores aquí.

leetbacoon
fuente