évaluation de l'expansion arithmétique en bash

13

La ligne suivante crée file_c-6.txtmais affiche 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 l' on supprime >elle la liste file_c-6.txtet de sortie 5:

Je ne comprends pas pourquoi il ne garde pas la valeur de idans le premier exemple.

$ 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
la source
4
c'est bizarre.
glenn jackman
2
Si j'utilise à la echoplace de ls, cela fonctionne de la deuxième façon dans les deux cas.
choroba
1
Ressemble quelque peu à l'exemple de code dans cette réponse .
Wildcard
4
/bin/echopréserve la différence, il semble donc que les redirections de sortie pour les commandes externes se produisent dans un sous-shell.
chepner
2
Vaut vraiment un rapport de bug à [email protected]; ce n'est pas corrigé dans 4.4, actuellement en cours de développement.
chepner

Réponses:

1

Si vous l'exécutez sous strace, vous pouvez voir que la version qui utilise lsdémarre la commande dans un sous-shell, où la version qui utilise echo exécute tout dans le shell existant.

Comparez la sortie de

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

contre

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

Vous verrez dans le premier:

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

Et dans le second:

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

Dans ce dernier exemple, vous voyez le clonedans un nouveau processus (de 1258 -> 1259), donc maintenant nous sommes dans un sous-processus. L'ouverture de file_c-6.txt, ce qui signifie que nous avons évalué $((++i))dans le sous-shell, et l'exécution de lsavec sa sortie standard définie sur ce fichier.

Enfin, nous voyons que le sous-processus se termine, nous récoltons l'enfant, puis nous continuons là où nous nous sommes arrêtés ... avec un $iensemble de 5, et c'est ce que nous répétons.

(N'oubliez pas que les modifications de variables dans un sous-processus ne se répercutent pas sur le processus parent, sauf si vous faites quelque chose explicitement dans le parent pour récupérer les modifications de l'enfant)

impythonique
la source
Excellente analyse. Une solution serait d'utiliser une variable temporaire pour la valeur incrémentée: i=5; j=$(( i + 1 )); ls file_a-${i}.txt file_b-${i}.txt > file_c-${j}.txt; i=${j}; echo $i.
Murphy