Friday, May 8, 2009

Episode #33: Recognizing Sub-Directories

Hal takes requests:

Loyal reader Lloyd Alvarez writes in with a little problem. He's writing Javascript that needs to get a directory listing and be able to easily discriminate sub-directories from other objects in the directory. The trick is that he needs code for both Unix/Linux and Windows. Well where else would you come for that kind of service than Command Line Kung Fu blog?

Turns out that Lloyd had doped out a Unix solution on his own:

$ ls -l | gawk '{printf("%s,",$1);for(i=9;i<=NF;i++) printf("%s ",$i);printf(":")}'
total,:drwxr-xr-x,dir1 :drwxr-xr-x,dir2 :-rw-r--r--,file1 :-rw-r--r--,file2 ...

Yow! That's some pretty ugly awk and even uglier output, but Lloyd was able to parse the resulting stream in his Javascript and easily pick out the directories.

But let me make that even easier for you, Lloyd old buddy:

# ls -F
dir1/ dir2/ file1 file2 otherdir1/ otherdir2/ otherfile1 otherfile2

Yep, the "-F" option causes ls to append a special character to each object in the directory to let you know what that object is. As you can see, directories have a "/" appended-- should be easy to pick that out of the output! Other suffix characters you might see include "@" for symbolic links, "*" for executables, and so on. Regular files get no special suffix (see the above output).

Maybe Lloyd would prefer to just get a list of the sub-directories without any other directory entries:

$ find * -maxdepth 0 -type d
dir1
dir2
otherdir1
otherdir2

Or if you're not into the find command:

$ for i in *; do [ -d $i ] && echo $i; done
dir1
dir2
otherdir1
otherdir2

Too many choices, but then that's Unix for you! I'll step out of the way now and let Ed shock Lloyd with the Windows madness.

Ed jumps in:

Good stuff, Lloyd! Thanks for writing in.

In Windows, the easiest way to do this is to use the dir command, with the /d option. That option is supposed to simply list things in columns, but it adds a nice little touch -- directory names now have square brackets [ ] on either side of them. Check it out:

C:\> dir /d
[.] [dir1] file1 [otherdir1] otherfile1
[..] [dir2] file2 [otherdir2] otherfile2

So, just find and parse out those brackets, Lloyd, and you should be good to go. Oh, and remove the . and .. if you don't want then. Unfortunately, we cannot eliminate them with a /b (for bare), because that removes the brackets.

For Hal's additional fu for looking for just directories, we can rely on the fact that Windows considers "directoriness" (is that a word?) as an attribute. So, we can list only the directories using:

C:\> dir /ad /b
dir1
dir2
otherdir1
otherdir2

Or, if you want only files (i.e., NOT directories):
C:\> dir /a-d /b
file1
file2
otherfile1
otherfile2


You could do this kinda stuff with FOR /D loops as well, which give you even more flexibility. For example, if you just want directory names with a slash after them, to give you similar output to Hal's "ls -F", you could run:

C:\> FOR /D %i in (*) do @echo %i/
dir1/
dir2/
otherdir1/
otherdir2/

Or, if you really like the colons that your current scripts parse, you could do:

C:\> FOR /D %i in (*) do @echo :%i

By altering that echo statement, you can roll the output however you'd like.

Fun, fun, fun!