#!/usr/bin/perl -w use strict; ## This declares some variables so that 'use strict' won't complain. ## $p is the directory to start with ## $s is the status of the -s flag ## $d is the status of the -d flag my ($p, $s,$d); ## Now, check to see if we were given -s, -d or any combination of ## the two. (@ARGV is the list of commandline arguments, but unlike ## C/C++, argv[0] is the first argument, not the program name. grep { # /-.?s/&&## $s++; /-.?d/ &&$d++;}@ARGV; ## See if we were given a directory on the commandline, or if we ## weren't, use . (($p = $ARGV[-1] || ".") !~ /^-/)|| ($p="."); ## Print the top directory of the tree print"$p\n"; ## Call the function that will print the contents of our top directory.. ## As an additional obfuscation, this subroutine is also named d (Perl ## recognizes $p, @p, p(), p and %p as different thingys) d($p,0,0); ## This is useless and just added to fill space. my$kr; ## This starts our subroutine. sub d{ ## We pull our subroutine arguments in via the @_ array. ## $p is the directory we want to read ## $x is ## $v is a bit vector representing whether or not to print a | ## character to the left. my($p,$x,$v)=@_; ## Open the directory handle D on directory $p. If we can't, ## issue a warning. opendir(D, $p)or warn"Can't read $p:$!" ; ## Declare the array @d. We'll use this later on to store directory ## contents. my at d; ## Now, here's where it gets far, far too hairy to maintain the tree ## formatting and adequately explain things. ## Anyway, here we have a map function. Map takes a code block, and ## a list (or array... there's a difference but it's subtle), and ## executes the code block for each element in the list, assigning ## $_ to represent that list element. It's a bit like xargs for ## you shell people. map { ## This declares and assigns some variables local to this ## scope. ## $k - the path of the current directory entry ## $r - used for spacing, and for storage of ' -> (realname) ## if we're a symbolic link. ## $y - whether or not this is the last entry in the current ## directory (as sorted) ## $l - used to hold the filename of the linked-to file if ## current dirent is a symlink. Initialzed to the empty ## string for aesthetic reasons. my($k,$r,$y,$l) = ("$p/$_", " ",($_ eq $d[-1]),''); ## Now, we print out "| " or " " depending on the number ## of nested directories above us. This is done by unpacking a ## bit vector, and displaying "|" for 1 and " " for 0. print map { ($_ ? "|":" ") ." "x3} (split(//,unpack("b*", $v)))[0..($x - 1)]; ## Now we check if the current dirent is a symlink. if(-l$k) { ## If so, read in it's target name ... $l = readlink($k);; ## ... and print it. If it doesn't exist, print it in () $r = ' -> '.(-e $l ? $l : "($l)"); ## Perl caches data from the last stat() or file test (as ## the underlying code for file tests like -l or -e does an ## implicit fstat() or lstat() call). Since we just performed ## tests on $l (the link target) we restore the stat cache ## to contain data on $k. This means we stat $k twice in ## the case of symlinks, but only once in the case of ## real files. -l $k; } ## Now, print everything. We print... print ## the "continuity line" up to our parent, using ## ` if we're the last entry, or | if we're not. $y ? '`' : '|', ## two -- to connect our entry to the line "-- ", ## If we want the file size, we print it. Otherwise, ## don't print anything here. $s ? sprintf("[%9d] ",-s _) : '', ## Now, print the filename. $_, ## and, if we're a symbolic link, $r will contain that ## information. Otherwise, it's a blank. $r, ## and, a newline. "\n"; ## Now, if the current dirent is not a symlink, and is a ## directory... if (!-l _ && -d _) { ## ... we set up our bit vector $v to contain either a 1 ## (if we're _not_ the last entry) or a 0 (if we are the ## last entry) so that we will print the | lines correctly. vec($v,$x,1) = !$y; ## now, make our recursive call with the current dirent, ## an incremented end to the bit vector, and the bit vector ## itself. d($k,$x+1,$v); }; } ## Oops... remember when I said I didn't use sort()? Well, I lied. ## Now, this is the array that the map() above is working on. ## (in Perl, an assignment returns the value assigned, in this case, ## an array). This is also where we populate @d with our directory ## entries. ## Reading this right to left, this reads all the directory entries ## from our dirhandle D, greps for the entries that aren't dotfiles, ## (and if we're running in -d mode, for any entries that are ## directories), and sorts them, storing the result in @d. ## This all gets used by the block of code above... ( @d = sort grep { ( -d"$p/$_" || ! $d ) && !/^\./ } readdir D); ## Finally, this ends our subroutine, and the program. }#DMO