[Top] [Prev] [Next] [Bottom]

9

# 9 Working with Files and Programs

This chapter describes how to run programs and access the file system from Tcl. These commands were designed for UNIX. In Tcl 7.5 they were implemented in the Tcl ports to Windows and Macintosh. There are facilities for naming files and manipulating file names in a platform-independent way so you can write scripts that are portable across systems. These capabilities enable your Tcl script to be a general-purpose glue that assembles other programs into a tool that is customized for your needs.

## Running Programs with exec

The exec command runs programs from your Tcl script.1 For example:

    set d [exec date]

The standard output of the program is returned as the value of the exec command. However, if the program writes to its standard error channel or exits with a non-zero status code, then exec raises an error. If you do not care about the exit status, or you use a program that insists on writing to standard error, then you can use catch to mask the errors:

    catch {exec program arg arg} result

The exec command supports a full set of I/O redirection and pipeline syntax. Each process normally has three I/O channels associated with it: standard input, standard output, and standard error. With I/O redirection you can divert these I/O channels to files or to I/O channels you have opened with the Tcl open command. A pipeline is a chain of processes that have the standard output of one command hooked up to the standard input of the next command in the pipeline. Any number of programs can be linked together into a pipeline.

  Using exec on a process pipeline.

    set n [exec sort < /etc/passwd | uniq | wc -l 2> /dev/null]

Example 9-1 uses exec to run three programs in a pipeline. The first program is sort, which takes its input from the file /etc/passwd. The output of sort is piped into uniq, which suppresses duplicate lines. The output of uniq is piped into wc, which counts the lines. The error output of the command is diverted to the null device to suppress any error messages.

Table 9-1 provides a summary of the syntax understood by the exec command.

Summary of the exec syntax for I/O redirection.

-keepnewline | (First argument.) Do not discard trailing newline from the result.
| | Pipe standard output from one process into another.
|& | Pipe both standard output and standard error output.
< fileName | Take input from the named file.
<@ fileId | Take input from the I/O channel identified by fileId.
<< value | Take input from the given value.
> fileName | Overwrite fileName with standard output.
2> fileName | Overwrite fileName with standard error output.
>& fileName | Overwrite fileName with both standard error and standard out.
>> fileName | Append standard output to the named file.
2>> fileName | Append standard error to the named file.
>>& fileName | Append both standard error and standard output to the named file.
>@ fileId | Direct standard output to the I/O channel identified by fileId.
2>@ fileId | Direct standard error to the I/O channel identified by fileId.
>&@ fileId | Direct both standard error and standard output to the I/O channel.
& | As the last argument, indicates pipeline should run in background.

Note that a trailing & causes the program to run in the background. In this case the process identifier is returned by the exec command. Otherwise, the exec command blocks during execution of the program and the standard output of the program is the return value of exec. The trailing newline in the output is trimmed off, unless you specify -keepnewline as the first argument to exec.

If you look closely at the I/O redirection syntax, you'll see that it is built up from a few basic building blocks. The basic idea is that | stands for pipeline, > for output, and < for input. The standard error is joined to the standard output by &. Standard error is diverted separately by using 2>. You can use your own I/O channels by using @.

### The auto_noexec Variable

The Tcl shell programs are set up during interactive use to attempt to execute unknown Tcl commands as programs. For example, you can get a directory listing by typing:

    ls

instead of:

    exec ls

This is handy if you are using the Tcl interpreter as a general shell. It can also cause unexpected behavior when you are just playing around. To turn this off, define the auto_noexec variable:

    set auto_noexec anything

### Limitations of exec on Windows

Windows 3.1 has an unfortunate combination of special cases that stem from console-mode programs, 16-bit programs, and 32-bit programs. In addition, pipes are really just simulated by writing output from one process to a temporary file and then having the next process read from that file. If exec or a process pipeline fails it is because of a fundamental limitation of Windows. (In Tcl 7.5, there were still bugs in some cases, but those were fixed in Tcl 7.6.)

The good news is that Windows 95 and Windows NT clean up most of these problems. Windows NT 4.0 is the most robust.

### Limitations of exec on Macintosh

The exec command is not provided on the Macintosh. The proper way to execute other programs is via AppleScript. Tcl 8.0 ships with an AppleScript extension that lets you control other Macintosh applications. As of this writing, the Tcl interface to the AppleScript extension is not finalized. You can find documentation at http://sunscript.sun.com/mac/AppleScript.html.

## The file Command

The file command provides several ways to check the status of files in the file system. For example, you can find out if a file exists, what type of file it is, and other file attributes. There are facilities for manipulating files in a platform-independent manner. Table 9-2 provides a summary of the various forms of the file command. They are described in more detail later. Note that the split, join, and pathtype operations were added in Tcl 7.5. The copy, delete, mkdir, and rename operations were added in Tcl 7.6. The attributes operation was added in Tcl 8.0

The file command options.

file atime name | Return access time as a decimal string.
file attributes name ?option? ?value? ... | Query or set file attributes. (Tcl 8.0)
file copy ?-force? source destination | Copy file source to file destination. The source and destination can be directories. (Tcl 7.6)
file delete ?-force? name | Delete the named file. (Tcl 7.6)
file dirname name | Return parent directory of file name.
file executable name | Return 1 if name has execute permission, else 0.
file exists name | Return 1 if name exists, else 0.
file extension name | Return the part of name from the last dot (i.e., .) to the end. The dot is included in the return value.
file isdirectory name | Return 1 if name is a directory, else 0.
file isfile name | Return 1 if name is not a directory, symbolic link, or device, else 0.
file join path path... | Join pathname components into a new pathname. (Tcl 7.5)
file lstat name var | Place attributes of the link name into var.
file mkdir name | Create directory name. (Tcl 7.6)
file mtime name | Return modify time of name as a decimal string.
file nativename name | Return the platform-native version of name. (Tk 8.0).
file owned name | Return 1 if current user owns the file name, else 0.
file pathtype name | relative, absolute, or driverelative. (Tcl 7.5)
file readable name | Return 1 if name has read permission, else 0.
file readlink name | Return the contents of the symbolic link name.
file rename ?-force? old new | Change the name of old to new. (Tcl 7.6)
file rootname name | Return all but the extension of name (i.e., up to but not including the last . in name).
file size name | Return the number of bytes in name.
file split name | Split name into its pathname components. (Tcl 7.5)
file stat name var | Place attributes of name into array var. The elements defined for var are listed in Table 9-3.
file tail name | Return the last pathname component of name.
file type name | Return type identifier, which is one of: file, directory, characterSpecial, blockSpecial, fifo, link, or socket.
file writable name | Return 1 if name has write permission, else 0.

.

## Cross-Platform File Naming

Files are named differently on UNIX, Windows, and Macintosh. UNIX separates file name components with a forward slash (/), Macintosh separates components with a colon (:), and Windows separates components with a backslash (\). In addition, the way that absolute and relative names are distinguished is different. For example, these are absolute pathnames for the Tcl script library (i.e., $tcl_library) on Macintosh, Windows, and UNIX, respectively:

Disk:System Folder:Extensions:Tool Command Language:tcl7.6

    c:\Program Files\Tcl\lib\Tcl7.6

/usr/local/tcl/lib/tcl7.6

The good news is that Tcl provides operations that let you deal with file pathnames in a platform-independent manner. The file operations described in this chapter allow either native format or the UNIX naming convention. The backslash used in Windows pathnames is especially awkward because the backslash is special to Tcl. Happily, you can use forward slashes instead:

    c:/Program Files/Tcl/lib/Tcl7.6

There are some ambiguous cases that can only be specified with native pathnames. On my Macintosh, Tcl and Tk are installed in a directory that has a slash in it. You can only name it with the native Macintosh name:

    Disk:Applications:Tcl/Tk 4.2

Another construct to watch out for is a leading // in a file name. This is the Windows syntax for network names that reference files on other computers. You can avoid accidentally constructing a network name by using the file join command described next. Of course, you can use network names to access remote files.

If you have to communicate with external programs, you may need to construct a file name in the native syntax for the current platform. You can construct these names with file join described later. You can also convert a UNIX-like name to a native name with file nativename.

Several of the file operations operate on pathnames as opposed to returning information about the file itself. You can use the dirname, extension, join, pathtype, rootname, split, and tail operations on any string; there is no requirement that the pathnames refer to an existing file.

### Building up Pathnames: file join

You can get into trouble if you try to construct file names by simply joining components with a slash. If part of the name is in native format, joining things with slashes will result in incorrect pathnames on Macintosh and Windows. The same problem arises when you accept user input. The user is likely to provide file names in native format. For example, this construct will not create a valid pathname on the Macintosh because $tcl_library is in native format:

    set file $tcl_library/init.tcl

Use file join to construct file names.

The platform-independent way to construct file names is with file join. The following command returns the name of the init.tcl file in native format:

    set file [file join $tcl_library init.tcl]

The file join operation can join any number of path name components. In addition, it has the feature that an absolute pathname overrides any previous components. For example (on UNIX), /b/c is an absolute pathname, so it overrides any paths that come before it in the arguments to file join:

    file join a b/c d

=> a/b/c/d

    file join a /b/c d

=> /b/c/d

On Macintosh, a relative pathname starts with a colon, and an absolute pathname does not. To specify an absolute path you put a trailing colon on the first component so it is interpreted as a volume specifier. These relative components are joined into a relative pathname:

    file join a :b:c d

=> :a:b:c:d

In the next case, b:c is an absolute pathname with b: as the volume specifier. The absolute name overrides the previous relative name:

    file join a b:c d

=> b:c:d

The file join operation converts UNIX-style pathnames to native format. For example, on Macintosh you get this:

    file join /usr/local/lib

=> usr:local:lib

### Chopping Pathnames: split, dirname, tail

The file split command divides a pathname into components. It is the inverse of file join. The split operation detects automatically if the input is in native or UNIX format. The results of file split may contain some syntax to help resolve ambiguous cases when the results are passed back to file join. For example, on Macintosh a UNIX-style pathname is split on slash separators. The Macintosh syntax for a volume specifier (Disk:) is returned on the leading component:

    file split "/Disk/System Folder/Extensions"

=> Disk: {System Folder} Extensions

Common reasons to split up pathnames are to divide a pathname into the directory part and the file part. These special cases are handled directly by the dirname and tail operations. The dirname operation returns the parent directory of a pathname, while tail returns the trailing component of the pathname:

    file dirname /a/b/c

=> /a/b

    file tail /a/b/c

=> c

For a pathname with a single component, the dirname option returns ".", on UNIX and Windows, or ":" on Macintosh. This is the name of the current directory.

The extension and root options are also complementary. The extension option returns everything from the last period in the name to the end (i.e., the file suffix including the period.) The root option returns everything up to, but not including, the last period in the pathname:

    file root /a/b.c

=> /a/b

    file extension /a/b.c

=> .c

## Manipulating Files and Directories

Tcl 7.6 added file operations to copy files, delete files, rename files, and create directories. In earlier versions it was necessary to exec other programs to do these things, except on Macintosh where cp, rm, mv, mkdir, and rmdir were built in. These commands are no longer supported on the Macintosh. Your scripts should use the file command operations described below to manipulate files in a platform-independent way.

File name patterns are not directly supported by the file operations. Instead, you can use the glob command described on page 106 to get a list of file names that match a pattern.

### Copying Files

The file copy operation copies files and directories. The following example copies file1 to file2. If file2 already exists, the operation raises an error unless the -force option is specified:

    file copy ?-force? file1 file2

Several files can be copied into a destination directory. The names of the source files are preserved. The -force option indicates that files under directory can be replaced:

    file copy ?-force? file1 file2 ... directory

Directories can be recursively copied. The -force option indicates that files under dir2 can be replaced:

    file copy ?-force? dir1 dir2

### Creating Directories

The file mkdir operation creates one or more directories:

    file mkdir dir dir ...

It is not an error if the directory already exists. Furthermore, intermediate directories are created if needed. This means you can always make sure a directory exists with a single mkdir operation. Suppose /tmp has no subdirectories at all. The following command creates /tmp/sub1 and /tmp/sub1/sub2:

    file mkdir /tmp/sub1/sub2

The -force option is not understood by file mkdir, so the following command accidentally creates a folder named -force, as well as one named oops.

    file mkdir -force oops

### Deleting Files

The file delete operation deletes files and directories. It is not an error if the files do not exist. A non-empty directory is not deleted unless the -force option is specified, in which case it is recursively deleted:

    file delete ?-force? name name ...

To delete a file or directory named -force, you must specify a non-existent file before the -force to prevent it from being interpreted as a flag (-force -force won't work):

    file delete xyzzy -force

### Renaming Files and Directories

The file rename operation changes a file's name from old to new. The -force option causes new to be replaced if it already exists.

    file rename ?-force? old new

Using file rename is the best way to update an existing file. First generate the new version of the file in a temporary file. Then use file rename to replace the old version with the new version. This ensures that any other programs that access the file will not see the new version until it is complete.

## File Attributes

There are several file operations that return specific file attributes: atime, executable, exists, isdirectory, isfile, mtime, owned, readable, readlink, size and type. Refer to Table 9-2 on page 94 for their function. The following command uses file mtime to compare the modify times of two files. If you have ever resorted to piping the results of ls -l into awk in order to derive this information in other shell scripts, you will appreciate this example:

  Comparing file modify times.

    proc newer { file1 file2 } {

    	if ![file exists $file2] {

    		return 1

    	} else {

    		# Assume file1 exists

    		expr [file mtime $file1] > [file mtime $file2]

    	}

    }

The stat and lstat operations return a collection of file attributes. They take a third argument that is the name of an array variable, and they initialize that array with elements that contain the file attributes. If the file is a symbolic link, then the lstat operation returns information about the link itself and the stat operation returns information about the target of the link. The array elements are listed in Table 9-3. All the element values are decimal strings, except for type, which can have the values returned by the type option. These are based on the UNIX stat system call. Use the file attributes command described later to get other platform-specific attributes:

Array elements defined by file stat.

atime | The last access time, in seconds.
ctime | The last change time (not the create time), in seconds.
dev | The device identifier, an integer.
gid | The group owner, an integer.
ino | The file number (i.e., inode number), an integer.
mode | The permission bits.
mtime | The last modify time, in seconds.
nlink | The number of links, or directory references, to the file.
size | The number of bytes in the file.
type | file, directory, characterSpecial, blockSpecial, fifo, link, or socket.
uid | The owner's user ID, an integer.

Example 9-3 uses the device (dev) and inode (ino) attributes of a file to determine if two pathnames reference the same file.

  Determining if pathnames reference the same file.

    proc fileeq { path1 path2 } {

    	file stat $path1 stat1

    	file stat $path2 stat2

    	expr $stat1(ino) == $stat2(ino) && \

    			$stat1(dev) == $stat2(dev)

    }

The file attributes operation was added in Tcl 8.0 to provide access to platform-specific attributes. The attributes operation lets you set and query attributes. The interface uses option-value pairs. With no options, all the current values are returned.

    file attributes book.doc

=> -creator FRAM -hidden 0 -readonly 0 -type MAKR

These Macintosh attributes are explained in Table 9-4. The 4-character type codes used on Macintosh are illustrated on page 432. With a single option, just that value is returned:

    file attributes book.doc -readonly

=> 0

The attributes are modified by specifying one or more option-value pairs. Setting attributes can raise an error if you do not have the right permissions:

    file attributes book.doc -readonly 1 -hidden 0

    Platform-specific file attributes.

    -permissions mode

    File permission bits. mode is a number with bits defined by the chmod system call. (UNIX)

    -group ID

    The group owner of the file. (UNIX)

    -owner ID

    The owner of the file. (UNIX)

    -archive bool

    The archive bit, which is set by backup programs. (Windows)

    -hidden bool

    If set, then the file does not appear in listings. (Windows, Macintosh)

    -readonly bool

    If set, then you cannot write the file. (Windows, Macintosh)

    -system bool

    If set, then you cannot remove the file. (Windows)

    -creator type

    type is 4-character code of creating application. (Macintosh)

    -type type

    type is 4-character type code. (Macintosh)

## Input/Output Command Summary

The following sections describe how to open, read, and write files. The basic model is that you open a file, read and/or write it, then close the file. Network sockets also use the commands described here. Socket programming is discussed in Chapter 16, and more advanced event-driven I/O is described in Chapter 15. Table 9-5 lists the basic commands associated with file I/O:

Tcl commands used for file access.

open what ?access? ?permissions? | Return channel ID for a file or pipeline.
puts ?-nonewline? ?channel? string | Write a string.
gets channel ?varname? | Read a line.
read channel ?numBytes? | Read numBytes bytes, or all data.
read -nonewline channel | Read all bytes and discard the last \n.
tell channel | Return the seek offset.
seek channel offset ?origin? | Set the seek offset. origin is one of start, current, or end.
eof channel | Query end-of-file status.
flush channel | Write buffers of a channel.
close channel | Close an I/O channel.

## Opening Files for I/O

The open command sets up an I/O channel to either a file or a pipeline of processes. The return value of open is an identifier for the I/O channel. Store the result of open in a variable and use the variable as you used the stdout, stdin, and stderr identifiers in the examples so far. The basic syntax is:

    open what ?access? ?permissions?

The what argument is either a file name or a pipeline specification similar to that used by the exec command. The access argument can take two forms, either a short character sequence that is compatible with the fopen library routine, or a list of POSIX access flags. Table 9-6 summarizes the first form, while Table 9-7 summarizes the POSIX flags. If access is not specified, it defaults to read. The permissions argument is a value used for the permission bits on a newly created file. The default permission bits are 0666, which grant read/write access to everybody. Example 9-4 specifies 0600 so that the file is only readable and writable by the owner. Remember to specify the leading zero to get an octal number as used in the chmod documentation. Consult the manual page on the UNIX chmod command for more details about permission bits.

  Opening a file for writing.

    set fileId [open /tmp/foo w 0600]

    puts $fileId "Hello, foo!"

    close $fileId

    Summary of the open access arguments.

     r

    Open for reading. The file must exist.

     r+

    Open for reading and writing. The file must exist.

     w

    Open for writing. Truncate if it exists. Create if it does not exist.

     w+

    Open for reading and writing. Truncate or create.

     a

    Open for writing. Data is appended to the file.

     a+

    Open for reading and writing. Data is appended.

    Summary of POSIX flags for the access argument.

    RDONLY

    Open for reading.

    WRONLY

    Open for writing.

    RDWR

    Open for reading and writing.

    APPEND

    Open for append.

    CREAT

    Create the file if it does not exist.

    EXCL

    If CREAT is also specified, then the file cannot already exist.

    NOCTTY

    Prevent terminal devices from becoming the controlling terminal.

    NONBLOCK

    Do not block during the open.

    TRUNC

    Truncate the file if it exists.

The following example illustrates how to use a list of POSIX access flags to open a file for reading and writing, creating it if needed, and not truncating it. This is something you cannot do with the simpler form of the access argument:

    set fileId [open /tmp/bar {RDWR CREAT}]

Catch errors from open.

In general you should check for errors when opening files. The following example illustrates a catch phrase used to open files. Recall that catch returns 1 if it catches an error, otherwise it returns zero. It treats its second argument as the name of a variable. In the error case it puts the error message into the variable. In the normal case it puts the result of the command into the variable:

  A more careful use of open.

    if [catch {open /tmp/data r} fileId] {

    	puts stderr "Cannot open /tmp/data: $fileId"

    } else {

    	# Read and process the file, then...

    	close $fileId

    }

### Opening a Process Pipeline

You can open a process pipeline by specifying the pipe character, |, as the first character of the first argument. The remainder of the pipeline specification is interpreted just as with the exec command, including input and output redirection. The second argument determines which end of the pipeline open returns. The following example runs the UNIX sort program on the password file, and it uses the split command to separate the output lines into list elements:

  Opening a process pipeline.

    set input [open "|sort /etc/passwd" r]

    set contents [split [read $input] \n]

    close $input

You can open a pipeline for both read and write by specifying the r+ access mode. In this case you need to worry about buffering. After a puts, the data may still be in a buffer in the Tcl library. Use the flush command to force the data out to the spawned processes before you try to read any output from the pipeline. You can also use the fconfigure command described on page 182 to force line buffering. Remember that read-write pipes will not work at all with Windows 3.1 because pipes are simulated with files. On UNIX, the expect extension, which is described in Exploring Expect (Libes, O'Reilly & Associates, Inc., 1995), provides a much more powerful way to interact with other programs.

Event-driven I/O is also very useful with pipes. It means you can do other processing while the pipeline executes, and just respond when the pipe generates data. This is described in Chapter 15.

## Reading and Writing

The standard I/O channels are already open for you. There is a standard input channel, a standard output channel, and a standard error output channel. These channels are identified by stdin, stdout, and stderr, respectively. Other I/O channels are returned by the open command, and by the socket command described on page 186.

There may be cases when the standard I/O channels are not available. Windows has no standard error channel. Some UNIX window managers close the standard I/O channels when you start programs from window manager menus. You can also close the standard I/O channels with close.

### The puts and gets Commands

The puts command writes a string and a newline to the output channel. There are a couple of details about the puts command that we have not yet used. It takes a -nonewline argument that prevents the newline character that is normally appended to the output channel. This will be used in the prompt example below. The second feature is that the channel identifier is optional, defaulting to stdout if not specified.

  Prompting for input.

    puts -nonewline "Enter value: "

    flush stdout				;# Necessary in Tcl 7.5 and Tcl 7.6

    set answer [gets stdin]

The gets command reads a line of input, and it has two forms. In the previous example, with just a single argument, gets returns the line read from the specified I/O channel. It discards the trailing newline from the return value. If end of file is reached, an empty string is returned. You must use the eof command to tell the difference between a blank line and end-of-file. eof returns 1 if there is end of file. Given a second varName argument, gets stores the line into a named variable and returns the number of bytes read. It discards the trailing newline, which is not counted. A -1 is returned if the channel has reached the end of file.

  A read loop using gets.

    while {[gets $channel line] >= 0} {

    	# Process line

    }

    close $channel

### The read Command

The read command reads blocks of data, which is often more efficient. There are two forms for read: you can specify the -nonewline argument or the numBytes argument, but not both. Without numBytes, the whole file (or what is left in the I/O channel) is read and returned. The -nonewline argument causes the trailing newline to be discarded. Given a byte count argument, read returns that amount, or less if there is not enough data in the channel. The trailing newline is not discarded in this case.

  A read loop using read and split.

    foreach line [split [read $channel] \n] {

    	# Process line

    }

    close $channel

For moderate-sized files it is about 10 percent faster to loop over the lines in a file using the read loop in the second example. In this case, read returns the whole file, and split chops the file into list elements, one for each line. For small files (less than 1K) it doesn't really matter. For large files (megabytes) you might induce paging with this approach.

### Platform-Specific End of Line Characters

Tcl automatically detects different end of line conventions. On UNIX, text lines are ended with a newline character (\n). On Macintosh they are terminated with a carriage return (\r). On Windows they are terminated with a carriage return, newline sequence (\r\n). Tcl accepts any of these, and the line terminator can even change within a file. All these different conventions are converted to the UNIX style so that once read, text lines are always terminated with a newline character (\n). Both the read and gets commands do this conversion.

During output, text lines are generated in the platform-native format. The automatic handling of line formats means that it is easy to convert a file to native format. You just need to read it in and write it out:

    puts -nonewline $out [read $in]

To suppress conversions, use the fconfigure command, which is described in more detail on page 183.

Example 9-10 demonstrates a File_Copy procedure that translates files to native format. It is complicated because it handles directories:

  Copy a file and translate to native format.

    proc File_Copy {src dest} {

    	if [file isdirectory $src] {

    		file mkdir $dest

    		foreach f [glob -nocomplain [file join $src *]] {

    			File_Copy $f [file join $dest [file tail $f]]

    		}

    		return

    	}

    	if [file isdirectory $dest] {

    		set dest [file join $dest [file tail $src]]

        }

    	set in [open $src]

    	set out [open $dest w]

    	puts -nonewline $out [read $in]

    	close $out ; close $in

    }

### Random Access I/O

The seek and tell commands provide random access to I/O channels. Each channel has a current position called the seek offset. Each read or write operation updates the seek offset by the number of bytes transferred. The current value of the offset is returned by the tell command. The seek command sets the seek offset by an amount, which can be positive or negative, from an origin which is either start, current, or end.

### Closing I/O channels

The close command is just as important as the others because it frees operating system resources associated with the I/O channel. If you forget to close a channel, it will be closed when your process exits. However, if you have a long-running program, like a Tk script, you might exhaust some operating system resources if you forget to close your I/O channels.

The close command can raise an error.

If the channel was a process pipeline and any of the processes wrote to their standard error channel, then Tcl believes this is an error. The error is raised when the channel to the pipeline is finally closed. Similarly, if any of the processes in the pipeline exit with a non-zero status, close raises an error.

## The Current Directory - cd and pwd

Every process has a current directory that is used as the starting point when resolving a relative pathname. The pwd command returns the current directory, and the cd command changes the current directory. Example 9-11 uses these commands.

## Matching File Names with glob

The glob command expands a pattern into the set of matching file names. The general form of the glob command is:

    glob ?flags? pattern ?pattern? ...

The pattern syntax is similar to the string match patterns:

- * matches zero or more characters
- ? matches a single character
- [abc] matches a set of characters.
- {a,b,c} matches any of a, b, or c.
- All other characters must match themselves.
The -nocomplain flag causes glob to return an empty list if no files match the pattern. Otherwise glob raises an error if no files match.

The -- flag must be used if the pattern begins with a -.

Unlike the glob matching in csh, the Tcl glob command only matches the names of existing files. In csh, the {a,b} construct can match non-existent names. In addition, the results of glob are not sorted. Use the lsort command to sort its result if you find it important.

  Finding a file by name.

    proc FindFile { startDir namePat } {

    	set pwd [pwd]

    	if [catch {cd $startDir} err] {

    		puts stderr $err

    		return

    	}

    	foreach match [glob -nocomplain -- $namePat]{

    		puts stdout [file join $startDir $match]

    	}

    	foreach file [glob -nocomplain *] {

    		if [file isdirectory $file] {

    			FindFile [file join $startDir $file] $namePat

    		}

    	}

    	cd $pwd

    }

The FindFile procedure traverses the file system hierarchy using recursion. At each iteration it saves its current directory and then attempts to change to the next subdirectory. A catch guards against bogus names. The glob command matches file names.

### Expanding Tilde in File Names

The glob command also expands a leading tilde (~) in filenames. There are two cases:

- ~/ expands to the current user's home directory.
- ~user expands to the home directory of user.
If you have a file that starts with a literal tilde, you can avoid the tilde expansion by adding a leading ./ (e.g., ./~foobar).

## The exit and pid Commands

The exit command terminates your script. Note that exit causes termination of the whole process that was running the script. If you supply an integer-valued argument to exit, then that becomes the exit status of the process.

The pid command returns the process ID of the current process. This can be useful as the seed for a random number generator because it changes each time you run your script. It is also common to embed the process ID in the name of temporary files.

You can also find out the process IDs associated with a process pipeline with pid:

    set pipe [open "|command"]

    set pids [pid $pipe]

There is no built-in mechanism to control processes in Tcl. On UNIX systems you can exec the kill program to terminate a process:

    exec kill $pid

## Environment Variables

Environment variables are a collection of string-valued variables associated with each process. The process's environment variables are available through the global array env. The name of the environment variable is the index, (e.g., env(PATH)), and the array element contains the current value of the environment variable. If assignments are made to env, they result in changes to the corresponding environment variable. Environment variables are inherited by child processes, so programs run with the exec command inherit the environment of the Tcl script. The following example prints the values of environment variables.

  Printing environment variable values.

    proc printenv { args } {

    	global env

    	set maxl 0

    	if {[llength $args] == 0} {

    		set args [lsort [array names env]]

    	}

    	foreach x $args {

    		if {[string length $x] > $maxl} {

    			set maxl [string length $x]

    		}

    	}

    	incr maxl 2

    	foreach x $args {

    		puts stdout [format "%*s = %s" $maxl $x $env($x)]

    	}

    }

    printenv USER SHELL TERM

    =>

    USER		= welch

    SHELL		= /bin/csh

TERM = tx

Note: Environment variables can be initialized for Macintosh applications by editing a resource of type STR# whose name is "Tcl Environment Variables". This resource is part of the tclsh and wish applications. Follow the directions on page 26 for using ResEdit. The format of the resource values is NAME=VALUE.

[Top] [Prev] [Next] [Bottom]

1 Unlike other UNIX shell exec commands, the Tcl exec does not replace the current process with the new one. Instead, the Tcl library forks first and executes the program as a child process.

Copyright © 1997, Brent Welch. All rights reserved.
This will be published by Prentice Hall as the 2nd Edition of
Practical Programming in Tcl and Tk