40

I was looking for the difference between the "." and "source" builtin commands and a few sources (e.g., in this discussion, and the bash manpage) suggest that these are just the same.

However, following a problem with environment variables, I conducted a test. I created a file testenv.sh that contains:

#!/bin/bash
echo $MY_VAR

In the command prompt, I performed the following:

> chmod +x testenv.sh
> MY_VAR=12345
> ./testenv.sh

> source testenv.sh
12345
> MY_VAR=12345 ./testenv.sh
12345

[note that the 1st form returned an empty string]

So, this little experiment suggests that there is a difference after all, where for the "source" command, the child environment inherits all the variables from the parent one, where for the "." it does not.

Am I missing something, or is this is an undocumented/deprecated feature of bash?

[ GNU bash, version 4.1.5(1)-release (x86_64-pc-linux-gnu) ]

| improve this question | | | | |
71

Short Answer

In your question, the second command uses neither the . shell builtin nor the source builtin. Instead, you are actually running the script in a separate shell, by invoking it by name like you would with any other executable file. This does give it a separate set of variables (though if you export a variable in its parent shell, it will be an environment variable for any child process, and therefore will be included in a child shell's variables). If you change the / to a space, then that would run it with the . built-in, which is equivalent to source.

Extended Explanation

This is the syntax of the source shell built-in, which executes the contents of a script in the current shell (and thus with the current shell's variables):

source testenv.sh

This is the syntax of the . built-in, which does do the same thing as source:

. testenv.sh

However, this syntax runs the script as an executable file, launching a new shell to run it:

./testenv.sh

That is not using the . built-in. Rather, . is part of the path to the file you are executing. Generally speaking, you can run any executable file in a shell by invoking it with a name that contains at least one / character. To run a file in the current directory, preceding it by ./ is thus the easiest way. Unless the current directory is in your PATH, you cannot run the script with the command testenv.sh. This is to prevent people from accidentally executing files in the current directory when they intend to execute a system command or some other file that exists in some directory listed in the PATH environment variable.

Since running a file by name (rather than with source or .) runs it in a new shell, it will have its own set of shell variables. The new shell does inherit the environment variables from the calling process (which in this case is your interactive shell) and those environment variables do become shell variables in the new shell. However, for an shell variable to be passed to the new shell, one of the following must be the case:

  1. The shell variable has been exported, causing it to be an environment variable. Use the export shell built-in for this. In your example, you can use export MY_VAR=12345 to set and export the variable in one step, or if it is already set you can simply use export MY_VAR.

  2. The shell variable is explicitly set and passed for the command you're running, causing it be an environment variable for the duration of the command being run. This usually accomplishes that:

    MY_VAR=12345 ./testenv.sh
    

    If MY_VAR is a shell variable that hasn't been exported, you can even run testenv.sh with MY_VAR passed as an environment variable by setting it to itself:

    MY_VAR="$MY_VAR" ./testenv.sh
    

./ Syntax for Scripts Requires a Hashbang Line to Work (Correctly)

By the way, please note that, when you invoke an executable by name as above (and not with the . or source shell built-ins), what shell program is used to run it is not usually determined by what shell you're running it from. Instead:

  • For binary files, the kernel may be configured to run files of that particular type. It examines the first two bytes of the file for a "magic number" that indicates what sort of binary executable it is. This is how executable binaries are able to run.

    This is, of course, extremely important, because a script can't run without a shell or other interpreter, which is an executable binary! Plus, many commands and applications are compiled binaries rather than scripts.

    (#! is the text representation of the "magic number" indicating a text executable.)

  • For files that are supposed to run in a shell or other interpreted language, the first line looks like:

    #!/bin/sh
    

    /bin/sh may be replaced with whatever other shell or interpreter is intended to run the program. For example, a Python program might start with the line:

    #!/usr/bin/python
    

    These lines are called hashbang, shebang, and a number of other similar names. See this FOLDOC entry, this Wikipedia article and Is #!/bin/sh read by the interpreter? for more information.

  • If a text file is marked executable and you run it from your shell (like ./filename) but it doesn't begin with #!, the kernel fails to execute it. However, seeing that this has happened, your shell will try to run it by passing its name to some shell. There are few requirements placed on what shell that is ("the shell shall execute a command equivalent to having a shell invoked..."). In practice, some shells--including bash*--run another instance of themselves, while others use /bin/sh. I highly recommend you avoid this and use a hashbang line instead (or run the script by passing it to the desired interpreter, e.g., bash filename).

    *GNU Bash manual, 3.7.2 Command Search and Execution: "If this execution fails because the file is not in executable format, and the file is not a directory, it is assumed to be a shell script and the shell executes it as described in Shell Scripts."

| improve this answer | | | | |
  • 2
    What i found useful on source is that the functions became available from bash without need to load or launch again. Example #!/bin/bash function olakease {echo olakease;}. Once you load it with source file.sh you can directly call olakease from bash. I really like that. Source executes then loads lot of things, the dot . is only for execution and is something like use bash file.sh – m3nda Jun 9 '15 at 3:49
  • 2
    @erm3nda . has this behavior as well: . file.sh and source file.sh do exactly the same thing, including retaining functions defined in file.sh. (Perhaps you are thinking of ./file.sh, which is different. But that does not use the . builtin; instead, . is part of the path.) – Eliah Kagan Feb 11 '17 at 12:28
  • Oh!, i didn't read carefully the .[space]file. Thank you so mach. – m3nda Feb 11 '17 at 13:30
14

Yes, you're missing something.

I think you're confusing the '.' that means current directory, as in ./testenv.sh and the '.' that means source (which is a built-in command). So in the case when '.' means source it would be . ./testenv.sh. Make sense?

So try this:

MY_VAR=12345 
. ./testenv.sh
| improve this answer | | | | |
  • 2
    The ./ tells it exactly where the file it, without it, bash will look through PATH first, then try the current dir if it did not find it. If bash is running in POSIX mode, and you don't provide a path to the file (like ./), it will only search in PATH, and fail to find the file if the current dir is not in PATH. – geirha Aug 30 '12 at 8:00
  • @geirha Yes, you're right, source (and .) actually will check $PATH first, even though they are not really running the script in the usual sense. My (former) comment was incorrect. – Eliah Kagan Aug 30 '12 at 9:31
  • Short and to the point +1 – David Morales Aug 25 '16 at 18:35

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.