Skip to content

Latest commit

 

History

History
131 lines (104 loc) · 4.68 KB

Bash - subshell.md

File metadata and controls

131 lines (104 loc) · 4.68 KB

Subshell:

When a command is executed in shell/bash inside a set of parenthesis it spawns a new process, which is Subshell.
  For ex:

  #!/bin/sh
  echo "output before exit"

  # This `subshell` will not cause the parent process to exit
  (exit 1)

  echo "output after exit"

If we run the above script, we will get the result like below:

  output before exit
  output after exit

This subshell (exit 1) did not result into parent shell to exit, because the subshell doesn’t have access to quit the parent shell.

We need to use signals to exit from the parent shell.

  #!/bin/sh

  trap "exit 1" 10
  PROC="$$"

  fatal(){
    echo "$@" >&2
    kill -10 $PROC
  }

  echo "output before exit"
  (fatal "no more output, script exiting")
  echo "output after exit"

This results in below output:

  output before exit
  no more output, script exiting

Signal:

What happends when we press ctrl+c on the unix/linux shell:

 Processes in *nix based operating systems can respond to signals to perform an action from outside the process.
 This is the mechanism that allows you to exit a command line process by pressing ctrl-c.
 When you press ctrl-c in your terminal, an interrupt signal, for ex: SIGINT, is sent to the current process.
 The process then responds to that signal by exiting (if it’s a well behaved process).

We can run trap -l to check all the signals supported by the system,

Here is the list:
 in the format: signal-pre-defined-number) signal-name

   1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
   6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
  11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
  16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
  21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
  26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
  31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
  38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
  43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
  48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
  53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
  58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
  63) SIGRTMAX-1  64) SIGRTMAX

How to respond to a signal using trap:

  #!/bin/sh

  # 2 is the SIGINT signal
  trap 'echo "received SIGINT"' 2
  # This could also be written as:
  # trap 'echo "received SIGINT"' INT

  # Note, SIGINT will be sent to child jobs, so the sleep
  # process will be interrupted when SIGINT is received.
  sleep 30

  echo "echo after sleep finishes"

This results in something like below, 'notice, we pressed ctrl+c i.e. ^C'

echo after sleep finishes

Example:
Save the following as test.sh and make it executable with chmod +x test.sh

  #!/bin/sh

  # fatal uses SIGUSR1 to allow clean fatal errors
  trap "exit 1" 10
  PROC=$$
  fatal(){
    echo "$@" >&2
    kill -10 $PROC
  }

  test -n "$1" || fatal "you must provide a path to a file as the first argument"

  test -f $1 || fatal "'${1}' isn't a file"

  (fatal "'${1}' pointed to a file, good job, but we're proving the subshell fatals too")

  echo "This should never output"

Notes:
 A subshell starts out as an almost identical copy of the original shell process.
 Under the hood, the shell calls the fork system call1, which creates a new process whose code and memory are copied.
 When the subshell is created, there are very few differences between it and its parent. In particular, they have the same variables.
 Even the $$ special variable keeps the same value in subshells: it's the original shell's process ID.
 Similarly $PPID is the PID of the parent of the original shell.

 A few shells change a few variables in the subshell. Bash sets BASHPID to the PID of the shell process, which changes in subshells.
 Bash, zsh and mksh arrange for $RANDOM to yield different values in the parent and in the subshell.
 But apart from built-in special cases like these,
 all variables have the same value in the subshell as in the original shell, the same export status, the same read-only status, etc.
 All function definitions, alias definitions, shell options and other settings are inherited as well.

Reference:

  1. https://cravencode.com/post/essentials/exit-shell-script-from-subshell/
  2. https://unix.stackexchange.com/a/138498