# Tcl Programming/Working with files

\_\_TOC\_\_

### Files and channels

In addition to the functionalities known from C\'s stdio, Tcl offers
more commands that deal with files, similar to what shells provide,
though often a bit more verbose. Here are some examples:

`glob *.tcl`

List all files in the current directory that match the pattern \*.tcl.

`file copy /from/path/file.ext /to/path`\
`.`\
`file delete /from/path/file.ext`\
`.`\
`file rename before.txt after.txt`\
`.`\
`cd /an/other/directory`\
`.`\
`pwd`

To let code temporarily execute in another directory, use this pattern:

`set here [pwd]`\
`cd $someotherdir`\
`#... code here`\
`cd $here`

More precisely, many \"file\" operations work in general on
\"channels\", which can be

-   standard channels (stdin, stdout, stderr)
-   files as opened with **open \...**
-   pipes as opened with *\'open \|\...*
-   sockets (TCP)

### File names

Files are often addressed with *path names*, which indicate their
position in the directory tree. Like Unix, Tcl uses \"/\" as path
separator, while on Windows \"\\\" is the native way - which brings
trouble not only to Tcl, but even to C, because \"\\\" is the escape
character on both, so that e.g. \\t is parsed as horizontal tab, \\n as
newline, etc. Fortunately Windows accepts natively \"/\" as well, so you
can use forward slash in both Tcl and C programs as path separator
without worries. However, you still have to take care of escape
sequences. One stopgap measure is

-   to escape the escape character, i.e. write \\\\ for \\, or
-   brace backslashed names, e.g. {\\foo\\bar\\grill.txt}

But Tcl allows to use the \"normal\" separator / in almost all
situations, so you\'re safer off with that. Unfortunately, things are
sad for Mac users, since MacOS (before X) accepts only \":\" as file
separator.

If you need to, here\'s ways to convert between the two forms:

`% file normalize \\foo\\bar\\grill.txt`\
`C:/foo/bar/grill.txt`\
`% file nativename /foo/bar/grill.txt`\
`\foo\bar\grill.txt`

You can even use file join command: *file join arg1 arg2 \... argN*

Tcl will then take care of all platform dependent details to create
platform independent path. For example:

*set somepath \[file join foo bar grill.txt\]*

will result in following path (on windows machine): foo/bar/grill.txt

### Input and output

Tcl\'s input/output commands are pretty closely based on those from C\'s
stdio (just strip off the leading *f*):

-   set handle \[**open** *filename ?mode?*\]
-   set data \[**read** *\$handle ?int?*\]
-   **tell** *\$handle*
-   **seek** *\$handle offset ?from?*
-   **puts** *?-nonewline? ?\$handle? content*
-   **gets** *\$handle ?varname?*
-   **close** *\$handle*

C\'s *printf* functionality is split in two steps:

-   format the data into a string with **format** (which is very much
    like *sprintf*)
-   output the resulting string with **puts**. For example,

`puts $handle [format "%05d %s" $number $text]`

To process a text file line by line, you have two choices. If the file
is smaller than several megabytes, you can read it just in one go:

`set f [open $filename]`\
`foreach line [split [read $f] \n] {`\
`    # work with $line here ...`\
`}`\
`close $f`

For files of any big size, you can read the lines one by one (though
this is slightly slower than the above approach):

`set f [open $filename]`\
`while {[gets $f line] >= 0} {`\
`    # work with $line here ...`\
`}`\
`close $f`\

Finally, if you can format your file so that it is executable Tcl code,
the following reading method is fastest:

`source $filename`

To \"touch a file\", i.e. create it if not exists, and in any case
update its modification time, you can use this:

`proc touch name {close [open $name a]}`

### \"Binary\" files

All files are made of bytes, which are made of bits, which are binary.
The term \"binary\" with files relates mostly to the fact that they can
contain bytes of any value, and line-ends (Carriage Return+Newline in
the DOS/Windows world) are not to be translated. Tcl can handle
\"binary\" files without a problem \-- just configure the channel as
binary after opening:

`set fp [open tmp.jpg]`\
`fconfigure $fp -translation binary`\
`set content [read $fp]`\
`close $fp`

Now the variable *content* holds the file\'s contents, byte for byte.

To test whether a file is \"binary\", in the sense that it contains NUL
bytes:

`proc binary? filename {`\
`   set f [open $filename]`\
`   set data [read $f 1024]`\
`   close $f`\
`   expr {[string first \x00 $data]>=0}`\
`}`

### The file command

Many useful operations with files are collected in the **file** command.
The first argument tells which operation to do:

-   file atime name ?time?
-   file attributes name
-   file attributes name ?option?
-   file attributes name ?option value option value\...?
-   file channels ?pattern? - returns the handles of currently open
    files
-   file copy ?-force? ?- -? source target
-   file copy ?-force? ?- -? source ?source \...? targetDir
-   file delete ?-force? ?- -? pathname ?pathname \... ?
-   file dirname name - e.g. \[file dirname /foo/bar/grill.txt\] -\>
    /foo/bar
-   file executable name
-   file exists name
-   file extension name - e.g. \[file extension /foo/bar/grill.txt\] -\>
    .txt
-   file isdirectory name
-   file isfile name
-   file join name ?name \...?
-   file link ?-linktype? linkName ?target?
-   file lstat name varName
-   file mkdir dir ?dir \...? - creates one or more directories
    (folders)
-   file mtime name ?time?
-   file nativename name
-   file normalize name
-   file owned name
-   file pathtype name
-   file readable name
-   file readlink name
-   file rename ?-force? ?- -? source target
-   file rename ?-force? ?- -? source ?source \...? targetDir
-   file rootname name - e.g. \[file rootname /foo/bar/grill.txt\] -\>
    /foo/bar/grill
-   file separator ?name?
-   file size name
-   file split name - e.g \[file split /foo/bar/grill.txt\] -\> {foo bar
    grill.txt}
-   file stat name varName
-   file system name
-   file tail name - e.g. \[file tail /foo/bar/grill.txt\] -\> grill.txt
-   file type name
-   file volumes - Windows: returns your \"drive letters\", e.g {A:/
    C:/}
-   file writable name