Monday, January 22, 2007

SpotlightFS

As many of you know, a little over a week ago Google announced the open sourcing of MacFUSE. FUSE allows functional file systems to be implemented in user-space programs, without the need to write any kernel code.

Amit already did all the hard work with MacFUSE; I just wanted to play with it. I thought it might be cool to stick a file system interface on Spotlight, so in my free time I came up with SpotlightFS. It is basically a MacFUSE file system that creates true smart folders, where the folders' contents are dynamically generated by querying Spotlight. This differs from Finder's version of smart folders, which are really plist files with a .savedSearch file extension. Since SpotlightFS smart folders are true folders, they can be used from anywhere—including the command line!

SpotlightFS is currently available for download from the MacFUSE Downloads page. Feel free to check it out.

Monday, January 15, 2007

Handling Filenames With Spaces

Typical Unix users cringe at the thought of putting spaces in file names. Mac users, on the other hand, frequently put spaces in file names because it's natural and may read better. This means that Mac OS X Unix geeks need to make sure their shell commands (and shell scripts) work correctly when faced with spaces in file names. Below I outline a few simple ways to properly deal with this.


  1. find(1) has a -exec option, which allows you to specify a command to be executed for each file found. The executed command may also take arguments, any of which may be the string {} which will be replaced by the path of the found file. The command and its arguments are not subject to further expansion of shell patterns, so it's safe for {} to stand in for a file with a space in the name. For example,
    $ find ~/Library -name '* *' -exec ls {} \;
    [... output omitted...]

    (Notice that our find command is looking for files with spaces in the name.)



  2. find's -exec option is OK for some situations, but since it forks a process to run the specified command for each file found, that can be a lot of unnecessary forking around. This is where find's -print0 combined with xargs -0 comes in. The idea here is that find will print out all the matching files, but instead of separating them by new lines, it will separate the files by the NULL byte ('\0' in C—the same character that terminates all strings in C). Then xargs -0 will read in strings that are separated by NULLs and will execute the specified command with as many paths from find as are allowed on the command line. The following command will create a tar file containing all the files in my Library folder that contain spaces in their names.
    $ find ~/Library -name '* *' -print0 | xargs -0 tar rf blah.tar
    $ tar tf blah.tar | wc -l
    1178



  3. Sometimes you need to do more than run one command on a filename. In this case, you'd like to use a loop to process each file. Maybe something like for file in $(find ~/Library -name '* *'); do [... body of loop ...]; done. The problem here is that the for-loop splits its input on white space (like the shell), filenames with spaces will be split up and treated as multiple files. One solution to this problem is to use a while-loop, and the read command. Something like the following should work.
    $ find ~/Library -name '* *' | while read filename
    > do
    > ls -ld "$filename"
    > cat "$filename"
    > [... whatever you want ...]
    > done

    [... output omitted...]

    This code works because the find outputs matching files one line at a time. read will read one line worth of data and assign it to the variable specified as its argument (in this case our variable is filename). Notice that within the loops body we need to quote the variable $filename when we use it.




There are other ways to deal with filenames with spaces, but these are the common techniques I find myself using the most.

Monday, January 08, 2007

Macworld '07

I'm super tired right now. I'm trying to get some last minute work done because I won't have any time tomorrow. I'm supposed to get up at 3am so I can pick some folks up at 4:15 and head up to Macworld. If we get there by 5am and I still get stuck in an overflow room, I'm not going to be too happy. Actually, some folks (about 15) I work with are camping out there tonight to ensure they get a seat! I guess I'm too lame for that.

Thursday, January 04, 2007

Use defaults to Manipulate Any Plist

I assume everyone knows about the defaults(1) command, and that it can be used from the command line to manipulate application's preferences. But what some folks may not know is that it can also be used to muck with almost any plist file. This can be very useful when tearing through plists from the command line or in a shell script.

For example, I can display TextEdit's CFBundleIdentifier with the following command.

$ defaults read /Applications/TextEdit.app/Contents/Info CFBundleIdentifier
com.apple.TextEdit

(Notice that I don't specify the .plist extension when referencing the file.)

Or I could see the services that TextEdit provides with this invocation.

$ defaults read /Applications/TextEdit.app/Contents/Info NSServices
(
{
NSMenuItem = {default = "TextEdit/New Window Containing Selection"; };
NSMessage = openSelection;
NSPortName = TextEdit;
NSSendTypes = (NSRTFDPboardType, NSRTFPboardType, NSStringPboardType);
},
{
NSMenuItem = {default = "TextEdit/Open Selected File"; };
NSMessage = openFile;
NSPortName = TextEdit;
NSSendTypes = (NSStringPboardType);
}
)


Not only can you read the plists, but you can also write to them using the normal defaults write /path/to/file key value technique.

However, I do have one question myself about this. In the TextEdit examples above, is there a way for me to specify that I want to read just the value of, say, the NSSendTypes key, which is in a dictionary, that is in an array, which is itself the value of the NSServices key? I'd love to be able to do this, but I'm not sure of the syntax (or if it's even possible).

Oh, and also take note of this warning from the man page.

WARNING: The defaults command will be changed in an upcoming
major release to only operate on preferences domains. General
plist manipulation utilities will be folded into a different
command-line program.

Tuesday, January 02, 2007

Unixjunkie Dashboard Widget

As you may have seen, Apple announced a beta of Dashcode today. Dashcode is an easy-to-use development environment for creating Dashboard widgets. It comes with a few cool widget templates, one of which is for creating simple widgets to display RSS feeds; so, clearly I had to spend the 10 clicks it took to make a simple widget for this blog.