evaluación de la expansión aritmética en bash

13

La siguiente línea crea file_c-6.txtpero genera 5:

$ i=5; ls file_a-${i}.txt file_b-${i}.txt > file_c-$(( ++i )).txt; echo $i
5
$ cat file_c-6.txt
file_a-5.txt
file_b-5.txt

Si se elimina >, se enumeraría file_c-6.txty generaría 5:

No puedo entender por qué no mantiene el valor de ien el primer ejemplo.

$ i=5; ls file_a-${i}.txt file_b-${i}.txt file_c-$(( ++i )).txt; echo $i
file_a-5.txt  file_b-5.txt  file_c-6.txt
6
Noil Noil
fuente
44
Eso es extraño.
Glenn Jackman
2
Si uso en echolugar de ls, funciona de la segunda manera en ambos casos.
choroba
1
Se ve algo similar al ejemplo de código en esta respuesta .
Comodín el
44
/bin/echoconserva la diferencia, por lo que parece que las redirecciones de salida para comandos externos suceden en una subshell.
chepner
2
Definitivamente vale la pena un informe de error a [email protected]; no está arreglado en 4.4, actualmente en desarrollo.
chepner

Respuestas:

1

Si ejecuta esto bajo strace, puede ver que la versión que usa lsinicia el comando en un subshell, donde la versión que usa echo lo ejecuta todo en el shell existente.

Compare la salida de

$ strace -f /bin/bash -o trace.txt -c 'i=5; echo $i; echo file_c-$((++i)).txt; echo $i'
5
6
6

en contra

strace -f /bin/bash -o trace.txt -c 'i=5; echo $i; ls > file_c-$((++i)).txt; echo $i'
5
5

Verás en el primero:

1251  execve("/bin/bash", ["/bin/bash", "-c", "i=5; echo $i; echo file_c-$(( ++"...], [/* 19 vars */]) = 0
...
1251  write(1, "5\n", 2)                = 2
1251  write(1, "file_c-6.txt\n", 13)    = 13
1251  write(1, "6\n", 2)                = 2

Y en el segundo:

1258  execve("/bin/bash", ["/bin/bash", "-c", "i=5; echo $i; ls > file_c-$(( ++"...], [/* 19 vars */]) = 0
...
1258  write(1, "5\n", 2)                = 2
...
1258  stat("/bin/ls", {st_mode=S_IFREG|0755, st_size=110080, ...}) = 0
1258  access("/bin/ls", R_OK)           = 0
1258  clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7301f40a10) = 1259
1259  open("file_c-6.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
1259  dup2(3, 1)                        = 1
1259  close(3)                          = 0
1259  execve("/bin/ls", ["ls"], [/* 19 vars */]) = 0
1259  write(1, "71\nbin\nfile_a-5.txt\nfile_b-5.txt"..., 110) = 110
1259  close(1)                          = 0
1259  munmap(0x7f0e81c56000, 4096)      = 0
1259  close(2)                          = 0
1259  exit_group(0)                     = ?
1259  +++ exited with 0 +++
1258  <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 1259
1258  rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f7301570d40}, {0x4438a0, [], SA_RESTORER, 0x7f7301570d40}, 8) = 0
1258  rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
1258  --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1259, si_status=0, si_utime=0, si_stime=0} ---
1258  wait4(-1, 0x7ffd23d86e98, WNOHANG, NULL) = -1 ECHILD (No child processes)
1258  rt_sigreturn()                    = 0
1258  write(1, "5\n", 2)                = 2

En este último ejemplo, verá cloneun nuevo proceso (desde 1258 -> 1259), por lo que ahora estamos en un subproceso. La apertura de file_c-6.txt, lo que significa que hemos evaluado $((++i))en el subshell, y la ejecución lscon su stdout establecido en ese archivo.

Finalmente, vemos que el subproceso sale, cosechamos al niño, luego continuamos donde lo dejamos ... con el $iconjunto a 5, y eso es lo que hacemos eco nuevamente.

(Recuerde que los cambios variables en un subproceso no se filtran al proceso padre, a menos que haga algo explícitamente en el padre para tomar los cambios del niño)

poco pitón
fuente
Excelente analisis. Una solución sería utilizar una variable temporal para el valor incrementado: i=5; j=$(( i + 1 )); ls file_a-${i}.txt file_b-${i}.txt > file_c-${j}.txt; i=${j}; echo $i.
Murphy