Search code examples
shellexit-codewaitpid

How does the Linux shell get the return code value for $? variable?


Does the Linux shell do a fork/exec and then waitpid() to get the return code to populate the $? variable, each time it executes something?


Solution

  • Yes, that's exactly what it does.

    You can see this for yourself, if you are interested, by running the shell under strace (a tool that intercepts and prints all system calls a program makes).

    strace bash -c '/usr/bin/ls > /dev/null; echo $?'
    

    This gives the following output, much trimmed:

    execve("/usr/bin/bash", ["bash", "-c", "/usr/bin/ls > /dev/null; echo $?"], [/* 58 vars */]) = 0
    [....]
    clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ff16c3d69d0) = 1189
    [....]
    wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 1189
    [....]
    --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1189, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
    wait4(-1, 0x7ffd3096b290, WNOHANG, NULL) = -1 ECHILD (No child processes)
    [....]
    exit_group(0)                           = ?
    +++ exited with 0 +++
    

    I used a bash script with two commands because otherwise bash doesn't fork but just execs the command, replacing itself (a useful optimization I just discovered it does!)

    In the output, clone is the system call behind the fork function, and likewise wait4 is called by waitpid. You don't see the exec because that happens in the child process, which we didn't ask strace to trace. If you add -f then it will do so, but will also trace the contents of the ls process.