13

The following line creates file_c-6.txt but outputs 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

If one removes > it would list file_c-6.txt and output 5:

I can't understand why it doesn't keep the value of i in the first example.

$ 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
  • 4
    that's bizarre. – glenn jackman Jan 22 '16 at 20:04
  • 2
    If I use echo instead of ls, it works the second way in both the cases. – choroba Jan 22 '16 at 20:11
  • 1
    Looks somewhat similar to the code example in this answer. – Wildcard Jan 22 '16 at 20:13
  • 4
    /bin/echo preserves the difference, so it seems like output redirections for external commands happen in a subshell. – chepner Jan 22 '16 at 20:14
  • 2
    Definitely worth a bug report to bug-bash@gnu.org; it's not fixed in 4.4, currently under development. – chepner Jan 22 '16 at 20:17
1

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)

  • Excellent analysis. One solution would be to use a temporary variable for the incremented value: i=5; j=$(( i + 1 )); ls file_a-${i}.txt file_b-${i}.txt > file_c-${j}.txt; i=${j}; echo $i. – Murphy Feb 20 '16 at 9:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.