If you run this under strace, you can see that the version that uses ls
starts up the command in a subshell, where the version which uses echo executes it all in the existing shell.
Compare the output of
$ strace -f /bin/bash -o trace.txt -c 'i=5; echo $i; echo file_c-$((++i)).txt; echo $i'
5
6
6
against
strace -f /bin/bash -o trace.txt -c 'i=5; echo $i; ls > file_c-$((++i)).txt; echo $i'
5
5
You'll see in the first:
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
And in the 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
In this last example, you see the clone
into a new process (from 1258 -> 1259), so now we're in a subprocess. The opening of file_c-6.txt which means that we've evaluated $((++i))
in the subshell, and the execution of ls
with its stdout set to that file.
Finally, we see that the subprocess exits, we reap the child, then we continue with where we left off... with $i
set to 5, and that's what we echo out again.
(Remember variable changes in a subprocess do not percolate up to the parent process, unless you do something explicitly in the parent to grab the child's changes)
echo
instead ofls
, it works the second way in both the cases. – choroba Jan 22 '16 at 20:11/bin/echo
preserves the difference, so it seems like output redirections for external commands happen in a subshell. – chepner Jan 22 '16 at 20:14sh
it works exactly as in your example. – rush Jan 22 '16 at 20:24((
and++i
and))
. maybe its nothing, butbash
's splitting on<>
redirections is always screwy and non-standard anyway. – mikeserv Jan 22 '16 at 23:22/bin/echo
yields the same result as described in the original question. So that seems to support the explaination offered by chepner. – Shevek Jan 22 '16 at 23:29bash
-only thing - I get the same behavior withzsh 5.2
– don_crissti Jan 23 '16 at 02:57i++
, nor++i
, but it process++i
as a case of double+i
. In dash, the file "written to" will be5
as well as the value printed. Instead, zsh and bash will "write to" file6
and print5
. That proves that++i
was interpreted as an increment but not kept in memory. – Jan 23 '16 at 04:25