help-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Help-bash] Different methods of running scripts; subshells and exec


From: Greg Wooledge
Subject: Re: [Help-bash] Different methods of running scripts; subshells and execute privileges
Date: Thu, 20 Aug 2015 08:31:42 -0400
User-agent: Mutt/1.4.2.3i

On Wed, Aug 19, 2015 at 05:42:59PM -0700, Michael Convey wrote:
> An interpreter script is a text file that has execute permission enabled
> and whose first line is of the form:
> 
> #! interpreter [optional-arg]

> ???When running a script via exec /path/script; because the first line of a
> bash script has the line "#! /bin/bash", isn't execve() ???just replacing the
> current bash shell by opening /bin/bash all over again (with the same PID)?

Yes.

> Isn't this essentially the same as creating a subshell?

No.  They are very different.

exec-ing a program causes all of your current non-environment variables
to go away.  All of your functions, all of your code, go away.  The only
things that are retained are the process ID, open file descriptors,
environment variables, resource limits, privileges, and signal handlers
(traps).

If you execute a script in the REGULAR way (without exec), what you get
is a child process.  This is not a "subshell" -- see below.  A child
process is the result of a fork() followed by an exec() in the child.
The child process has a brand new PID, and inherits the parent's
environment variables and open file descriptors, but runs independently.
The parent may continue running at the same time (if the child is
"asynchronous" or "background", usually in bash as a result of putting
the & character at the end of the command), or in the more common case,
the parent may sit around waiting for the child to finish before the
parent can resume (in this case, the child process is called "synchronous"
or "foreground").

A subshell is also a child process but it has a very specific meaning
in bash programming.  A subshell is the result of a fork() NOT followed
by an exec().  A subshell inherits copies of everything the parent
has -- environment variables, NON-environment variables, functions,
loop states, everything.  It's a perfect clone.

Subshells may be created explicitly by putting ( ) around a command,
or more commonly, they are created implicitly by pipelines, or by a
command substitution.

Usually subshells are a pain in the ass that you have to know about and
work around.  For example, consider this code:

echo "$myvariable" | read a b c

You just wanted to split the variable into three words, right?  Well,
this doesn't work because the read takes place in a subshell (because
of the pipeline).  Variables a, b and c do get the correct values,
but those values are discarded when the pipeline ends.  You have to
understand this and work around it.

read a b c <<< "$myvariable"

That one works, because read runs in the current process instead of in
a subshell.

I still don't understand what you are trying to DO.  We can keep answering
theoretical questions all day, but this won't help you until we know what
you're trying to accomplish, so we can skip all the theory and just tell
you "Oh, you do it like this."

Here, let me write up a little mini-FAQ.

=== What is the shebang for? ===

The shebang (#!/usr/bin/env bash or #!/bin/sh or whatever) at the top of
a file tells the KERNEL that when this file is executed as a program,
the kernel should launch the specified interpreter and pass the file as
an argument.

Thus, if you have a file named /usr/local/bin/ascript which has proper
permissions and begins with #!/bin/sh and you type "ascript" at the
command line, the kernel will execute "/bin/sh /usr/local/bin/ascript".

When /bin/sh starts reading /usr/local/bin/ascript, it will see the
shebang as a comment (a line starting with #) and ignore it.

=== What's the difference between "foo" and "exec foo" and "source foo"? ===

When you run "foo", bash looks for an alias, a function, or a builtin
by that name.  If it doesn't find one, it searches the directories in PATH
for a file named foo.  If it finds one, it will try to execute it as
a program (using a fork() followed by an exec()).  The kernel runs foo
however it sees fit based on foo's magic number (the first few bytes).
If foo happens to be a compiled program in ELF format, the kernel launches
the ELF loader.  If foo happens to be a script beginning with a shebang,
the kernel launches the shebang interpreter.

When you run "exec foo", you are telling bash to skip the fork() and just
do the exec().  The current bash shell and script vanish, to be replaced
by the new program foo.  foo may be another script, or a compiled program;
it doesn't matter.  The results are the same.

When you run "source foo" you are telling bash to read the lines of the
file foo (which it finds by performing a PATH search) as if they had
been part of the script that it's currently reading.  There is no fork()
and there is no exec().  Bash just starts reading commands from a new
file, continues until the end of that file, then resumes reading the
original script.

(Actually, bash treats "source" as a sort of function call.  You can
do a "return" command to stop reading a sourced script and resume reading
the original script.)

=== What's a subshell? ===

A subshell is a fork() without an exec().  The new child process is an
exact copy of the parent, with copies of all of the parent's variables.
Any changes made in the subshell are lost when the child process ends.

Subshells are created by putting ( ) around a command, or implicitly by
pipelines and command substitutions.

myfunc() { foo=bar; echo ok; }
foo=foo
x=$(myfunc)
#  At this point, foo still equals foo.  The change to foo during myfunc
#  happened in a subshell (implicitly created by the command substitution),
#  so it is lost.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]