Updated 2016-12-06 02:41:21 by AMG

The env array is one of the magic names created by Tcl to reflect the value of the invoker's environment; that is to say, when one starts Tcl, what one does is make a request to the operating system to start a new process. If the process is being started directly, that process is typically given a set of variables called environment variables which reflect certain default values. When Tcl starts, it creates the env array and reads the environment. Each environment variable becomes a key in this array whose value is the value of the environment variable.

For instance, on Unix, one will typically find keys like

  • PATH: series of file system directory prefixes, typically separated by :, which the operating system will apply when asked to execute a file with an incomplete pathname.
  • HOME: name of the user's login directory.
  • LANG: string used to specify internationalization info.

To access the env array within a Tcl proc, one needs to tell the proc that env is a global array. There are two ways to do this.

  • $::env(PATH)
  • global env ; puts $env(PATH) can alternatively used to identify that the variable is globally available; however, you have to remember to use the global command in EVERY proc that you need to use env. Using the $::env notation is shorter.

Note that iterating over the env array with array startsearch/array anymore/array nextelement may not work right. These commands are deprecated in any case (see array).

aspect 2014-09-11: Note also that changes to an upvared element of ::env may not work right. This seems to be a result of the issue discussed under "Variable Traces vs Upvar" on trace, as EnvTraceProc in generic/tclEnv.c relies on the name1 and name2 parameters. This bug looks like it has existed for a looong time.

The variables that appear in $::env are only those variables which the parental shell places into the environment. Under bash/ksh/sh this is done by a line such as:
 MYVAR="this is a test" ; export MYVAR

while in csh/tcsh, you do it like this:
  setenv MYVAR "this is a test"

Shell variables set but not put into the environment are not accessible by child processes.

Also note that while a Tcl program can set new variables or values in $::env , those values will NOT be reflected in the program's parent process.

This separation of process data is an intentional part of the various operating systems available today. To communicate new variables or variable values back into the parent, one needs some sort of out of band communication of information; perhaps writing a shell script or file of data which the parent shell then reads.

DKF - Additional notes

The env array is precisely the environment variables of the current process. If you want to pass a variable to a child process, setting it in env is the way to do it. And on Windows, you can't set an environment variable to an empty string, as the OS defines that to be the same as removing the variable completely. (Stupid, but that's the way things are, and working around it - which is possible BTW - is not a good idea as it can cause trouble for some programs to receive empty env-vars when they don't expect it. Keeping the tight binding is thought to be a better idea.)

LV During this thread [1] you will see that attempting to set env variables to null is going to result in behavior that your code may not expect. So it is better to not even try to do this on Windows, rather than attempt to write around the quirks.

There is only one env array per process. All interpreters in all threads see the same data (though IIRC safe interpreters don't see the env array at all) and a change made by one can be seen by all. This makes access to the array quite slow, and I don't recall if there are cross-interpreter/cross-thread traces (though I suspect not, so you can't use it as an inter-thread communication mechanism. Use something designed for the task instead).

LV How does one code a check to see if an environment variable like DISPLAY is available? When I code:
 if { [info exists $::env(DISPLAY) ] } {
        package require Tk

the application ceases because the variable doesn't exist.

escargo This might be a question I can answer. Your code is doing two things:

  1. Retrieving the value of env(DISPLAY) in the global name space.
  2. Checking to see if the value exists.

You are working too hard. This code just checks to see if the (array) variable exists. (It does not check the value. I suppose to be air tight somebody needs to check to ensure that the value of DISPLAY is of the form expected by X Windows. For that matter, is the value of DISPLAY set on the MacOS/MacOS X or Microsoft Windows? Or maybe your code is not intended to run cross platform.)
 if { [info exists ::env(DISPLAY) ] } {
        package require Tk

How does one actually change an environment variable with Tcl, so that the change is reflected in the program's parent process/ parental shell in Windows?

LV I don't do much on Windows, but most environments do not allow a child to modify a parent process (perhaps for security reasons, but certainly for stability reasons you don't want this to happen). About the best you could do is have the child write out a file that the parent, upon completion of the child, executes in its own process space to effect the change.

More on this subject appears in "Setting environment variables with a script".

MG March 7th 2006 - One odd thing about the env array worth noting is that - at least for me on Win XP Home SP 2 - the var names are NOT case-sensitive. That is:
  % parray env windir
  env(windir) = C:\WINDOWS
  % parray env WINDIR

So $env(windir) is set, and $env(WINDIR) is not. Then..
  % set env(windir)
  % set env(WINDIR)
  % set env(WiNdIr)

And [info exists], etc., all report the var existing, no matter what case is used for the key name. I believe this is because the underlying OS does not treat environment variables in a case-sensitive manner, but I find it curious that although Tcl only sets them with one case (as per [array names] or [parray]), they can be accessed using any case.

fredderic: I'm guessing the answer to that can be found also in DKF's comment:

  • The env array is precisely the environment variables of the current process.

I take that to mean that env(), if a real array at all, is at least backed by a read trace handler which looks up requested variables in the real environment array, possibly also updating the real environment array with any new values submitted into the env() array. It would therefore tend to take on the semantics of the system under which it's running.

RS 2007-02-22: One can use temporary env variables to control a Tcl script from the command line, at least in Unixoid systems including Cygwin. Example scriptlet:
 set foo 42
 if [info exists env(DO)] {eval $env(DO)}
 puts foo=$foo

This script will typically report

To remote-control it without editing, set the DO variable before the call:
 $ DO='set foo 4711' tclsh myscript.tcl

which will evidently report

LV 2007 June 21: Are there any env variables that are created by Tcl auto-magically? That is, certainly I understand that if in a script I execute a set command, I can create an env variable. But are there any that Tcl creates behind the scenes?

[Rahul] - 2009-09-30 04:25:58

Hello....i am running tcl from my linux.(csh)....i need to send the value of a variable from tclsh top the parent shell...can anyone tell me if this is possible and if it is...suggest a way..:'(

pcam I think this is probably not the best way to come about your issue. Have a read here [2] and here [3] and the posts seem to provide some guidance about a similar problem. What about you detail why you need to do this and I am sure someone will have advice on how to proceed, and I bet there is a way out.

LV - 2009-09-30 08:46:06

Since tclsh runs as a child to the parent, it cannot directly add a variable to the parent. There are several approaches that can be taken, but these involve some sort of interprocess communication.

For instance, the parent could start the child, reading the child's stdout, and then create or update a variable based on information produced by the child.

Or the parent could start the child, the child create a file, database table, shared memory/message queue/etc. containing the information needed, and then the parent read the created entity and set the variable itself.

Or the child could start its own child, similar to the parent, but with the variable defined.

Or the parent could listen to a socket, and the child then write to that socket to send along information.

There are other such options. It is likely all follow the same pattern.

snichols - 2010-03-17 12:30:00

Here is a short Tcl script that generates a report of environment variables. I found the report useful when porting my build environment settings to a new computer.
    global env
    foreach key [array names env] {
        puts "$key=$env($key)"

pw - 2010-09-03 14:20:00

Here is an even shorter one ;)
    parray ::env

snichols - 2010-11-7 11:17:00

Clever one liner. Ran it, but can't copy the report into other programs such as batch files.

See also edit