1

My question is a twofold.

First:

Is it possible to achieve this without using a subshell?

FOO=$((6-5))

or this?

BAR=`echo "$FOO/100" | bc -l`

If I understand the second one correctly, I'm creating 2 subshells by using ´ and |

Second

Does creating/using subshells for this kind of stuff impact the overall performance of a script?

-- Thanks

Community
  • 1
  • 1
Lord Otori
  • 349
  • 1
  • 4
  • 17
  • Sure every time you fork, it costs something. You could use expr instead of a pipe to bc. BAR=\`expr $FOO / 100\` – Scott Presnell May 01 '13 at 04:20
  • 4
    As far as I know, the `$(( ))` syntax is evaluated in the *current* shell ([manual](http://www.gnu.org/software/bash/manual/bashref.html#Arithmetic-Expansion)) – glenn jackman May 01 '13 at 04:32
  • @ScottPresnell Thanks for your comment, but note I'm using bc -l which uses the maximum available decimal precision. – Lord Otori May 01 '13 at 16:42
  • @glennjackman Thanks, it looks like it is. I just assumed it spawned a new process. – Lord Otori May 01 '13 at 16:44

2 Answers2

3

You can check count of subprocess allocated with that simple line:

bash -c 'echo $$'

It create new shell and outputs current process id.

Since processes in linux are sequential numbers, we can use this "hack" to detect how many processes was started between commands. (some processes can be started in background by cron or at, so need to check results multiple times).

Also this command will start process, so if you start it multiple time you will see increasing number. To get real count of processes that was started between this command you must substract 1.

So starting checking.

$ bash -c 'echo $$'
4240
$ FOO=$((6-5))
$ bash -c 'echo $$'
4241

4241 - 4240 - 1 = 0. No subprocess was started.

$ FOO=1111
$ bash -c 'echo $$'
4244
$ BAR=`echo "$FOO/100" | bc -l`
$ bash -c 'echo $$'
4248

4248 - 4244 - 1 = 3. There is 3 process was started.

If we start with "here is a string" removing useless echo:

$ bash -c 'echo $$'
4256
$ BAR=`bc -l <<< "$FOO/100"`
$ bash -c 'echo $$'
4259

4259 - 4256 - 1 = 2. Now 2 subprocess was started.

seems like echo implicitly allocates new shell??

  1. backticks allocates new shell - new process to read output

  2. bc allocates new process

This variant will create two subprocess too:

read BAR < <(bc -l <<< "$FOO / 100")
  1. read is a bash command - it does not fork subprocess and executed in the same shell

  2. () will create shell - subprocess

  3. bc will create subprocess

loentar
  • 5,111
  • 1
  • 24
  • 25
  • You have to be careful using $$ as an indicator of a child process. In Bash, subshells are child processes, but retain the same $$. See also http://stackoverflow.com/questions/14686872/sub-shell-diffferences-between-bash-and-ksh – cdarke May 01 '13 at 07:04
  • You can also use `strace -e trace=process bash -c 'echo $((3 + 5))'` (at least under Linux) to view any process-related system calls. This should confirm that `$(( ... ))` does not cause a new process to be forked. – chepner May 01 '13 at 12:21
  • @loentar Thanks for such an amazing answer, you have no idea how much it helps me. I did not even know <<< existed... – Lord Otori May 01 '13 at 16:47
2

One way to see that $(( ... )) does not invoke a subshell is to modify the value of a variable inside the construct, and see that the change is visible in the current shell.

$ count=5
$ : $(( count++ ))
$ echo $count
6

If a subshell was created by $(( ... )), the output of the following echo would still be 5.

chepner
  • 497,756
  • 71
  • 530
  • 681