Updated 2018-05-02 15:02:56 by pooryorick

file, a built-in Tcl command, manipulates file names and attributes

See Also  edit

Additional file commands
directory recursion
presents various ways of walking a file hierarchy
filesystem benchmarking
fileutil
Tcllib module that includes commands to determine the format of a file, iterate through contents, search contents, replace in contents, make temporary files and directories, normalize paths, determine relative paths, etc.
How do I remove one line from a file?
file and directory change notifications

Documentation  edit

man file

Synopsis  edit

file option name ?arg arg ...?

Description  edit

This command provides several operations on a file's name or attributes. Name is the name of a file; if it starts with a tilde, then tilde substitution is done before executing the command (see the manual entry for filename for details). Option indicates what to do with the file name. Any unique abbreviation for option is acceptable. The valid options are: (see complete man page; here's only notes on selected options).
file atime name ?time?
file attributes name
file attributes name ?option?
file attributes name ?option value option value...?
file channels ?pattern?
file copy ?-force? ?--? source target
file copy ?-force? ?--? source ?source ...? targetDir
file delete ?-force? ?--? pathname ?pathname ... ?
file dirname name
file executable name
file exists name
file extension name
file isdirectory name
file isfile name
file join name ?name ...?
file link ?-linktype? linkName ?target? (requires Tcl8.4)
file lstat name varName
file mkdir dir ?dir ...?
file mtime name ?time?
file nativename name
file normalize name (requires Tcl8.4)
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
file separator ?name?
file size name
file split name
file stat name varName
file system name (requires Tcl8.4)
file tail name
file tempfile ?varName? ?template? (requires Tcl8.6)
file type name
file volumes
file writable name

File mode  edit

Unix style

returns something like drwxr--r--
proc file_mode fn {
    file stat $fn t
    if [file isdirectory $fn] {
        set prefix "d"
    } else {
        set prefix "-"
    }
    set s [format %03o [expr $t(mode)%512]]
    foreach i {  0   1   2   3   4   5   6   7} \
        j {--- --x -w- -wx r-- r-x rw- rwx} {
            regsub -all $i $s $j s
    }
    return $prefix$s
} ;#RS

file absolute  edit

To derive an absolute name for any file name:
file join [pwd] $filename

Check whether a directory is writable  edit

proc dir'writable dir {
    set name $dir/[clock clicks]
    if [catch {open $name w} fp] {return 0}
    close $fp
    file delete $name
    return 1
} ;# RS

[Hopefully someone will provide some samples to show how these are useful. With the new VFS support in Tcl 8.4, and an appropriate extension, file can operate on much more than just local files (see the One-line web browser in Tcl for example).]

Can someone comment on what happens if the current working directory is on a different drive?

Yes. The question is essentially nonsensical because the current working directory is a per-drive attribute. What happens if I try to open the door, but the other door is open?

Hmm , so the following sequence, in Windows, is nonsense?
cd C:/tcl/lib
set fd [open G:list.txt w]
set lst [glob *]
puts $fd $lst
close $fd

Interesting- I never realized that Windows was so brain-damaged.

LES: One may want to use this proc to catch a quick grasp of and/or memorize all the file commands:
#! /bin/env tclsh

proc fileinfo {myFile} {
    puts  "file name: $myFile"
    puts  "exists: [ file  exists  $myFile ]"
    puts  "----------------"
    puts  "type: [ file  type  $myFile ]"
    puts  "size: [ file  size  $myFile ]"
    puts  ""
    puts  "atime: [ file  atime  $myFile ]"
    puts  "mtime: [ file  mtime  $myFile ]"
    puts  ""
    puts  "pathtype: [ file  pathtype  $myFile ]"
    puts  "dirname: [ file  dirname  $myFile ]"
    puts  "separator: [ file  separator  $myFile ]"
    puts  "nativename: [ file  nativename  $myFile ]"
    puts  "normalize: [ file  normalize  $myFile ]"
    puts  "rootname: [ file  rootname  $myFile ]"
    puts  "tail: [ file  tail  $myFile ]"
    puts  "extension: [ file  extension  $myFile ]"
    puts  "join: [ file  join  $myFile ]"
    puts  ""
    puts  "attributes: [ file  attributes  $myFile ]"
    puts  "owned: [ file  owned  $myFile ]"
    puts  "readable: [ file  readable  $myFile ]"
    puts  "writable: [ file  writable  $myFile ]"
    puts  "executable: [ file  executable  $myFile ]"
    puts  ""
    puts  "channels: [ file  channels  $myFile ]"
    puts  "system: [ file  system  $myFile ]"
}

IL: problems identifying what you're looking at? the following proc returns a simple string detailing what you have available to you. This is not great coding, but makes up for some TCL 8.3 ambiguities that might lead a programmer astray due to some assumptions. It also should rid you of the trailing slash problem.
proc setloctype { loc { ambigtofile "false" } } {

    #   define a location as a directory or file or both.
    #   also identifies location as absolute or relative 
    #
    #   * manually choose to prefer files over directories when resolving ambiguous references if needed
    #
    #   examples:
    #       /mydirectory/name - absolute_dir
    #       logs/reports.log - relative_file_dir
    #       /restartfarms.sh - absolute_file
    
    set tailtype ""      

    if { [string range $loc end end] == "/" } {
    
        set tail ""
        set loc [string range $loc 0 end-1]
        set tailtype "dir"
        
    } else {
    
        set tail [file tail $loc]
        if { [regexp "\\." $loc] || [string is true $ambigtofile] } {
            set tailtype "file"
        } else {
            set tailtype "dir"        
        }
    }

    switch [file pathtype $loc] {

        "absolute" {
            switch [regexp -all "/" $loc] {
                "1" { 
                    if { $tailtype == "file" } {
                        set loctype "absolute_file" 
                    } else {
                        set loctype "absolute_dir"
                    }
                }
                default { 
                    if { $tailtype == "file" } {
                        set loctype "absolute_file_dir"
                    } else {
                        set loctype "absolute_dir"
                    }
                }
            }
        }

        "relative" - 
        default {
            switch [regexp -all "/" $loc] {
                "0" { 
                    if { $tailtype == "file" } {
                        set loctype "relative_file" 
                    } else {
                        set loctype "relative_dir"
                    }
                }
                default { 
                    if { $tailtype == "file" } {
                        set loctype "relative_file_dir" 
                    } else {
                        set loctype "relative_dir"
                    }                
                }
            }
        }
    }

    return $loctype
}

JH: I wanted to get a relative path based on another path and couldn't find a proc that did it, so here is one I made (with minimal testing):
proc relpath {basedir target} {
    # Try and make a relative path to a target file/dir from base directory
    set bparts [file split [file normalize $basedir]]
    set tparts [file split [file normalize $target]]

    if {[lindex $bparts 0] eq [lindex $tparts 0]} {
        # If the first part doesn't match - there is no good relative path
        set blen [llength $bparts]
        set tlen [llength $tparts]
        for {set i 1} {$i < $blen && $i < $tlen} {incr i} {
            if {[lindex $bparts $i] ne [lindex $tparts $i]} { break }
        }
        set path [lrange $tparts $i end]
        for {} {$i < $blen} {incr i} {
            set path [linsert $path 0 ..]
        }
        return [eval [list file join] $path]
    }

    return $target
}

# Some examples
relpath C:/Tcl/lib/tcllib C:/Tcl/lib/tcllib/module/pkgIndex.tcl
# module/pkgIndex.tcl
relpath [file dirname [info nameofexe]] [info library]
# ../lib/tcl8.4
relpath C:/Tcl/foo D:/Tcl/bar
# D:/Tcl/bar
relpath /usr/bin /usr/local/bin/tclsh
# ../local/bin/tclsh

MG Edited so that it runs file normalize on both paths first - still works the same with all the examples above, but also handles, for instance
% cd "c:/program files/tcl/"
% relpath . [info library]
lib/tcl8.4

when one path is relative and one isn't. I was going to suggest
return [join $path [file separator]]

instead of
return [eval [list file join] $path]

to remove the eval, but for me (on Windows XP) that returns a different result - file separator returns a \, while file join joins using a /. Using
return [file normalize [join $path [file separator]]]

would remove the need for eval and still return the same result as file join, I think. Is it intended that they (file separator and file join) use different characters on Windows? The docs for both say they use the "native character for the platform"...

JH: The file join and file separator differences are intentional. Note that you can use file nativename to also get native again.

Csan How about a [file mkfifo] instead of [exec mkfifo]? Would be a lifesaver for us, un*x users...

MHo: I'm confused:
% glob -types hidden -dir //wa101su052.prod01.hmkintra.de/c\$ *
{//wa101su052.prod01.hmkintra.de/c$/boot.ini} {//wa101su052.prod01.hmkintra.de/c$/bootfont.bin} {//w
a101su052.prod01.hmkintra.de/c$/Config.Msi} {//wa101su052.prod01.hmkintra.de/c$/IO.SYS} {//wa101su05
2.prod01.hmkintra.de/c$/MSDOS.SYS} {//wa101su052.prod01.hmkintra.de/c$/NTDETECT.COM} {//wa101su052.p
rod01.hmkintra.de/c$/ntldr} {//wa101su052.prod01.hmkintra.de/c$/pagefile.sys} {//wa101su052.prod01.h
mkintra.de/c$/System Volume Information}
% glob -types hidden -dir //wa101su052.prod01.hmkintra.de/c\$ pagefile.sys
no files matched glob pattern "pagefile.sys"
% file size //wa101su052.prod01.hmkintra.de/c\$/pagefile.sys
could not read "//wa101su052.prod01.hmkintra.de/c$/pagefile.sys": no such file or directory
%

Does this mean we can't operate on hidden and/or system files (MS Win)? While glob lists 'pagefile.sys', the file command ignores it...

LV On a Windows XP system to which I've access, I tried starting tkcon and then typed
(bin) 53 % glob -types hidden -dir $env(HOME) *

and I got back a variety of hits. I then typed:
(bin) 54 % file size {C:\Documents and Settings\lvirden/NTUSER.DAT}
10747904

So it appears that file can work on files identified as hidden.

However, on a Unix system, I tried:
$ tclsh8.5
% glob -types hidden -dir /tmp *
/tmp/. /tmp/.. /tmp/.A /tmp/.X11-unix /tmp/.X11-pipe /tmp/.__emcp.conf_lock /tmp/.rc7027 /tmp/.dp7027 /tmp/.co7027 /tmp/.rs7027 /tmp/.de7027
% glob -types hidden -dir /tmp .A
no files matched glob pattern ".A"
% glob -types hidden -dir /tmp /tmp/.A
no files matched glob pattern "/tmp/.A"
% glob -types hidden -dir /tmp .X11-unix
/tmp/.X11-unix
% ^D
$ ls -ld /tmp/.A
drwxr-xr-x   2 root     root         178 Sep  6 22:25 /tmp/.A

Ah - in this case, glob saw that was not a "file" but a directory.

So, I wonder, in your case, if glob is for some reason not seeing pagefile.sys as something other than a file? Perhaps either permissions on the item prevent file from getting the size, or it sees it as a "device" or something instead of a file...

Notice that glob kept saying "no files matched"?

LV Does anyone have any idea how to get functionality comparable to file to work when one's tcl script has to run as a set-userid program? An example of this would be a program which needs to read a sensitive login and password from a file that should not be readable to the user normally. While certainly that one function could be farmed out to a second program, there are still times when the process needs to be able to perform functions with an effective user-id.

[The catch is that] file readable tests only the real user id and not the effective user id. But it seems like it would be useful for file to have at least an option to check the effective id before checking the real one...

AMG: A script to check if two strings name the same file would be handy, even in the face of /., /.., ~, ~user, //, relative paths, mixing backslashes on Windows, and combinations thereof resulting in an infinite variety of possible non-canonical representations. Variations include considering symbolic and hard links to be the same or different files.

For challenge points, what about the case of the same filesystem being mounted in multiple places? What about Linux --bind mounts and Windows NT junctions [1]? What about Windows drive letters in the multiple ways in which they may be specified or omitted? What about Windows UNC paths which allow multiple ways to name the same share? Things can get arbitrarily hairy.

PYK 2014-06-05: Here ya go:
proc samefile? {fname1 fname2} {
    file stat $fname1 finfo1 
    file stat $fname2 finfo2
    return [expr $finfo1(dev) eq $finfo2(dev) && $finfo1(ino) eq $finfo2(ino)]
}

AMG: Ah, I didn't realize the dev and ino stat fields existed in Windows. It seems they do. Thanks!

Wonder if it would be appropriate to suggest this approach on the main script page for telling whether the current script is the same as $argv0.

PYK: that hadn't occurred to me at all, but it would be the more solid than the other approaches mentioned on that page. Since you suggested it I'll let you have first crack at it. The whole methodology for determining the main script is a little vexing, since none of them would detect something like source info script. Something like a boolean info main? would be cleaner.

AMG: Done.