hmmm....

Finding Stuff in Your Local CPAN Mirror

So you’ve got this great new local CPAN you can use to install stuff you know about but how do you find stuff you don’t know about? There are two main ways I inspect the contents of my local CPAN mirror. The first is a simple shell function for searching package names, the second, has a bit more to it but we will save that for later.

grep to the Rescue

One of the metadata files in our local mirror is /modules/02packages.details.txt.gz. Let’s take a look inside.

/modules/02packages.details.txt.gz
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
File:         02packages.details.txt
URL:          http://www.perl.com/CPAN/modules/02packages.details.txt
Description:  Package names found in directory $CPAN/authors/id/
Columns:      package name, version, path
Intended-For: Automated fetch routines, namespace documentation.
Written-By:   CPAN::Mini::Inject 0.33
Line-Count:   145223
Last-Updated: Tue, 12 Aug 2014 15:01:40 GMT

AAA::Demo                         undef  J/JW/JWACH/Apache-FastForward-1.1.tar.gz
AAA::eBay                         undef  J/JW/JWACH/Apache-FastForward-1.1.tar.gz
AAAA::Crypt::DH                    0.04  B/BI/BINGOS/AAAA-Crypt-DH-0.04.tar.gz
AAAA::Mail::SpamAssassin          0.002  S/SC/SCHWIGON/AAAA-Mail-SpamAssassin-0.002.tar.gz
AAAAAAAAA                          1.01  M/MS/MSCHWERN/AAAAAAAAA-1.01.tar.gz
AAC::Pvoice                        0.91  J/JO/JOUKE/AAC-Pvoice-0.91.tar.gz
AAC::Pvoice::Bitmap                1.12  J/JO/JOUKE/AAC-Pvoice-0.91.tar.gz
AAC::Pvoice::Dialog                1.01  J/JO/JOUKE/AAC-Pvoice-0.91.tar.gz
...

The format is pretty straightforward. After a few header lines, we have one line per package seen by the indexer. The lines consist of three whitespace separated columns of a package name, current package version (if the indexer found one), and latest distribution containing it. The AAC-Provoice and Apache-FastForward distributions show us that each individual package within the distribution is listed. We could easily use zgrep to find packages that match a pattern.

/modules/02packages.details.txt.gz
1
2
3
4
5
$ zgrep -i tiny ~/Dropbox/minicpan/modules/02packages.details.txt.gz
Acme::Has::Tiny                   0.001  T/TO/TOBYINK/Acme-Has-Tiny-0.001.tar.gz
Acme::Module::Build::Tiny          0.0   D/DA/DAGOLDEN/Acme-Acme-Module-Build-Tiny-0.06.tar.gz
Acme::Tiny                        0.003  E/ET/ETHER/Acme-Tiny-0.003.tar.gz
...

This is a little long to type regularly. A shell alias isn’t appropriate as we want to sandwich the provided input between zgrep and the path to the file. Fortunately, a shell function will allow us to do this without much more verbosity.

1
2
3
function cpangrep {
    zgrep $* /home/yourusername/Dropbox/minicpan/modules/02packages.details.txt.gz
}

Dropping this line in your .zshrc or .bashrc will allow you to search 02packages.details.txt.gz with the cpangrep command.

1
2
3
4
5
6
7
8
$ cpangrep tiny | wc -l
21
$ cpangrep -i tiny | wc -l
344
$ cpangrep -i tiny
Acme::Has::Tiny                   0.001  T/TO/TOBYINK/Acme-Has-Tiny-0.001.tar.gz
Acme::Module::Build::Tiny          0.06  D/DA/DAGOLDEN/Acme-Acme-Module-Build
...

Since we use $* instead of $1, all arguments we give to the function are passed on to zgrep. This allows us to do things like search with or without ignoring case on demand.

CPAN::Mini::Webserver

CPAN::Mini::Webserver provides a script that launches a web server providing an interface to your local minicpan. Usage couldn’t be easier, install CPAN::Mini::Webserver then run minicpan_webserver. By default the server binds to port 2963 but you can change this with the --port argument.

If you’ve used search.cpan.org or metacpan, there should be no surprises in how to navigate around the site. The documentation has information on a few things you can plug in to your .minicpanrc to enable things like full text indexing.

Here are a few screenshots of CPAN::Mini::Webserver to wet your appetite.

Taking CPAN With You

CPAN is THE killer feature for Perl. One problem you may have from time to time due to it’s online nature is availability issues. How do you install CPAN modules from a plane, train, or automobile without wifi? Or from the middle of nowhere in Oregon (Ione, OR)? Or timely install Moose and it’s dependencies from a dial-up connection in the middle of the deserted Oklahoma plains (Leedey, OK). Well, my fellow perler, I have been to these places forgotten by the Internets, and I have installed CPAN modules with impunity.

CPAN::Mini

CPAN::Mini, driven by the included minicpan, creates a local mirror of CPAN containing only the latest non-developer release of every distribution on CPAN. With CPAN::Mini you too can install CPAN modules with impunity from exotic locations. But just how much disk space must you dedicate for such awesomeness? My mirror is currently just under 3 GB.

Start by installing CPAN::Mini with your CPAN client of choice:

1
2
3
4
5
$ cpanm CPAN::Mini
$ # or
$ cpanp install CPAN::Mini
$ # or
$ cpan CPAN::Mini

Now you need a config file in ~/.minicpan.

~/.minicpanrc
1
2
3
4
local:   ~/Dropbox/minicpan
remote: http://cpan.hexten.net/
also_mirror: indices/ls-lR.gz
skip_perl: 1

These options should be fairly self-explanatory. Check out the CPAN Mirrors site to find a healthy mirror in your region for the second line. Now, just run minicpan and wait. Periodically, you’ll want to run this command again to download new dists and delete old ones. That’s all it takes to be the owner of a shiny tiny local CPAN mirror.

Using Your Shiny New CPAN Mirror

To use your new mirror, just specify the path to your CPAN::Mini mirror in your CPAN client of choice.

cpanm

For cpanm I have an alias in my .zshrc:

1
minicpanm='cpanm --mirror ~/Dropbox/minicpan --mirror-only'

The same line should work in a .bashrc as is. Then, to install a new CPAN module, just run minicpanm ACME::urmom.

cpanp

Run cpanp s edit and your editor will launch. If the file you are in isn’t empty, skip to the next step. Now run cpanp s save to copy the current configuration to the user config file and then run cpanp s edit again.

Find the hosts config and add your new entry at the beginning.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$conf->set_conf( hosts => [
      {
        'host' => '',
        'path' => '/Users/yourname/Dropbox/minicpan',
        'scheme' => 'file'
      }
      {
        'host' => 'ftp.cpan.org',
        'path' => '/pub/CPAN/',
        'scheme' => 'ftp'
      },
      {
        'host' => 'www.cpan.org',
        'path' => '/',
        'scheme' => 'http'
      },
# ...

Save and exit your editor.

cpan

For cpan, you use the interactive shell to update the configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ cpan
cpan shell -- CPAN exploration and modules installation (v2.00)
Enter 'h' for help.

cpan[1]>  o conf urllist unshift file:///Users/yourname/Dropbox/minicpam
Please use 'o conf commit' to make the config permanent!


cpan[2]> o conf urllist
    urllist
    0 [file:///Users/yourname/Dropbox/minicpam]
    1 [http://cpan.erlbaum.net/]
    2 [http://cpan.mirror.clemson.edu/]
    3 [http://cpan.develooper.com/]
Type 'o conf' to view all configuration items


cpan[3]> o conf commit
commit: wrote '/Users/yourname/.local/share/.cpan/CPAN/MyConfig.pm'

Enjoy (Faster) CPAN Installs From Anywhere

That’s it! From here on out your disk speed and not your Internet connection, or lack thereof, will be your throttle in your CPAN client of choice acquiring distribution packages.

Listening to Two Icecast Streams at Once

I run a site with two scanner feeds for my local town, Gallowaynow.com, one for EMS & Fire and another less publicized one for PD. I regularly listen to both of these feeds when I’m not around the scanners supplying them. How do you listen to two feeds that may be talking at the same time and make sense of anything? With sox the Swiss Army knife of sound processing. By playing the two mono feeds out each channel of a stereo device, the brain can easily deferenciate between the two sources. This may not work very well from laptop speakers but works great from speakers with a few feet of seperation or headphones. sox can easily handle modifying the streams likes to work with files or piped streams so first we need to grab the icecast stream.

fIcy

fIcy is an open source suit of tools for grabbing icecast streams. It can do things like grab a stream and save it to mp3s when metadata changes, checkout the examples on its site for some of the other neat things it can do. Rather than invoke fIcy directly, I use fPls, included in the suite which can retry if the connection drops. Invocation is fairly straight forward:

1
fPls -T 10 -L-1  http://gallowaynow.com:8000/stream.m3u -t

The -T 10 waits ten seconds between reconnection attempts, -L-1 tells it to try indefenitly, next is the stream URL, and -t outputs the stream metadata to STDERR when it changes.

sox

sox is crazy flexible and can manipulate audio files in almost any way imaginable. See the examples in the man page or this blog post for just a sampling (heh, sampling) of the sort of things sox can do. In our case we want to take a mono mp3 on STDIN and place that audio in just one channel of a stereo stream and then play it. We use the remix effect to accomplish this. Since transforming and then playing the audio is common, sox includes a play binary that takes all the same commands sox does but then plays the output.

1
play -q -t mp3 - remix 1 0

Remix takes a space separated list of input channels to be output on the positional output channel. In this case, input channel 1 is output on the first channel, the second output channel uses the special input channel 0, silence. For our second stream, we flip the numbers around to specify that the input be played on the second output channel. You can also adjust the volume of the input channel in the output. My actual command for my first stream is:

1
play -qV1 -t mp3 - remix 1v0.3 0

In this case we are attenuating the voltage of the signal by 70%. 2 instead of 0.3 would double volume. This lets me adjust the streams for equivalent volume.

play can take any filter chain you can pass to sox, another useful one here is silence trimming. By adding silence -l 1 0.1 1% -1 2.0 1% to the end of our play command line, we can ask sox to trim any silence longer than 2 seconds to 2 seconds. This may seem odd since we are listening in real time, not recording to a file, but it can help with minimizing delay. Due to packet loss, slow connections, congested links, etc, the stream may stutter at times. Since a large portion of the time the stream is silent, these stutters aren’t noticable to the ear but it means playback was silenced for a few seconds and then resumed. Any of these delays add up and eventually the audio you are listening to could be as much as 5 or 10 seconds behind the metadata printed by fIcy. By telling sox it’s okay to throw away silence, we can keep the audio caught up, minimizing delays.

We can put the commands together in simple shell scripts to simplify calling them.

ems.sh
1
2
3
4
#!/bin/sh

fPls -v -T 10 -L-1  http://gallowaynow.com:8000/stream.m3u -t \
    | play -qV1 -t mp3 - remix 1v0.3 0 silence -l 1 0.0 1% -1 2.0 1
pd.sh
1
2
3
4
#!/bin/sh

fPls -v -T 10 -L-1  http://gallowaynow.com:8000/gtpd.m3u -t \
    | play -qV1 -t mp3 - remix 0 1 silence -l 1 0.0 1% -1 2.0 1

tmux

Since the metadata is visible, it shows exactly which unit is talking and on which channel, running our two commands in a split tmux window makes since. Especially if you have screen real estate to put this terminal out of the way somewhere. We can script the creation of this window with a simple shell script.

tmux-streams.sh
1
2
3
4
5
6
7
8
#!/bin/sh

tmux new-session -d -s streams
tmux send-keys './ems.sh' 'C-m'
tmux select-window -t streams:0
tmux split-window -v
tmux send-keys './pd.sh' 'C-m'
tmux -2 attach-session -t streams

I like send-keys instead of specifying the command to run directly as it lets us ctrl+c the script and then rerun it with up-arrow enter. Handy if I tweak the script or want to launch a fresh copy of stuff instead of having to destroy the tmux session.

tmux screenshot

The Often Overlooked Perl Module Pod::Usage

Pod::Usage is one of those boring modules that doesn’t get the attention it deserves. Included in Perl core since 5.6.0, released fourteen years ago, it’s not a new kid on the block. It’s job isn’t sexy, so not lots of talks, blog posts, or tweets about it. There are only 5 ++s for the distribution on metacpan.org. It’s my goto module for all but the most trivial scripts, I probably use it second to only pragmas in scripts. (Haha, I made a funny.)

Pod::Usage generates usage/help output for your scripts by extracting relevant bits from your POD. This means you write usage/help information once for the POD and you get both your man page and -h command line help. Let’s look at its usage by starting with the result. I’ll use otfile, a simple script for serving a file via HTTP from the command line for these examples.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ otfile -h
Usage:
    $ otfile [-a -p 1234] <file to serve>

Options:
    --auto or -a
            Auto port selection. Increments specified port until successful.

    --port=N or -p N
            Use specified port, defaults to 1234.

    --multiple or -m
            Don't exit after serving the file the first time. To serve a
            file to multiple people. Requires, CTL+C to exit.

    --help or -h
            this help information

$

Nothing exceptional here, this is the style and content you would expect from any script on a POSIX like system. We get similarly unsurprising output for incorrect arguments.

1
2
3
4
5
6
$ otfile --invalid-arg-named-yaakov
Unknown option: invalid-arg-named-yaakov
Usage:
    $ otfile [-a -p 1234] <file to serve>

$

So what does the source look like? One of Pod::Usage’s benefits is the simplicity in using it.

1
2
3
4
use Pod::Usage;
use Getopt::Long;

GetOptions( ..., 'help' => sub { pod2usage(1) } ) or pod2usage(2);

That’s it in terms of code! You can substitute Getopt::Std or your other argument parser of choice just as easily. This was really cheating though since the real meat of it is in the POD. So anything special there?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
=head1 SYNOPSIS

$ otfile [-a -p 1234] <file to serve>

=head1 OPTIONS

=over 8

=item B<--auto> or B<-a>

Auto port selection.  Increments specified port until successful.

=item B<--port=N> or B<-p N>

Use specified port, defaults to 1234.

=item B<--multiple> or B<-m>

Don't exit after serving the file the first time. To serve a file to multiple
people. Requires, CTL+C to exit.

=item B<--help> or B<-h>

this help information

=back

Nothing special in the POD. Pod::Usage with the source I used outputs either the POD’s SYNOPSIS section or the SYNOPSIS section and any section titled OPTIONS, ARGUMENTS or OPTIONS AND ARGUMENTS.

That’s it! Now, head over to Pod-Usage on metacpan.org and give it some ++ love.

Logging Apple Battery Data With Perl

Inspired by a blog post and the code that goes with it at jradavenport/batlog, I created a perl script for logging Apple laptop battery details last August.

My script logs 11 values using ~67 bytes per record. The oldest log I have begins 288 days ago and is 17,122,966 bytes for an average of 59.4 KB/day or 21 MB/year. The log is missing data points due to things like prolonged hibernation or sleep, theoretical max is around 35MB/year with 10 minute resolution.

The repo is on GitHub.

Sample Graphs

A simple gnuplot script is included in the repo which produces the below graphs.

A couple days of output: Sample Battery Graph

9 months of output: Sample Battery Graph 9 Months

Things get a bit squashed at 9 months but the most interesting value over long durations, battery health, is clearly visible.

The Code

Acquiring the data for this in Perl is exceedingly simple. Apple provides a binary called ioreg for interacting with the I/O Kit Registry. Asking for battery information with the arguments -rc AppleSmartBattery gets us almost JSON:

1
2
3
4
5
6
7
8
9
+-o AppleSmartBattery  <class AppleSmartBattery, id 0x100000203, registered, matched, active, busy 0 (0 ms), ret$
    {
      "TimeRemaining" = 36
      "AvgTimeToEmpty" = 65535
      "InstantTimeToEmpty" = 65535
      "ExternalChargeCapable" = Yes
      "FullPathUpdated" = 1401701615
      "CellVoltage" = (4193,4182,4193,0)
      ...

For our simple needs, massaging the data into proper JSON to use a parser is unnecessary. A regex can cleanly capture the names inside the quotes and their values. We then store the name value pairs in a hash.

1
2
3
if ( $line =~ m/"([^"]+)" = (.*)$/ ) {
    $data{$1} = $2;
}

We create an array of the values we actually care about logging. This is done near the beginning of the script, before running and parsing the output of ioreg. If the amount of data was much larger, our simple parser could be made smarter to only store in the hash the values we care about. With only 39 values though, the slightly increased memory usage in the short lived cron job is preferable to the little bit of extra CPU to check if we want a value before storing it.

1
2
3
4
my @logged = ( qw(
        CurrentCapacity MaxCapacity Pcnt CycleCount LifePcnt
        ExternalConnected IsCharging FullyCharged Temperature CellVoltage
) );

We can then take advantage of hash slices to write out just the values we care about:

1
say $fh join( "\t", time, @data{@logged} );

@data{@logged} becomes a list of the hash values for the keys earlier specified in @logged.

The full script weighs in at only 28 lines of straight forward code. See just it here.

GitHub Repo

In Perl, Debugging With Binary Data Structures

Perl has some really great tools for dumping datastructures when debugging. Data::Printer by GARU is a realitive new comer that’s gained a tremendous following. Of course, everyone also knows Data::Dumper, core since at least perl 5.5 in 2004. What do you do when you need to dump binary data though?

I recently had to do this for the first time so I went looking on the CPAN to see what was available. I found two modules that both made dumping binary data simple. Data:HexDump and a more recently updated Data::HexDumper. Both have similiary simple usage:

1
2
3
4
5
6
7
use Data::Hexdumper;
use Data::HexDump;

my $binary_data = get_some_bin_data();

print hexdumper $data;
print hexdump $data;

I started with the much more configurable and more recent Data::HexDumper. The only draw back was some of my data generated warnings when printing. You could turn this off with either of:

1
2
3
print hexdumper (data => $binary_data, suppress_warnings => 0);
# or
print hexdumper $binary_data, { suppress_warnings => 0 };

but both were two much typing for a lazy Perl developer. I ended up installing and switching over to Data:HexDump. This module has no configurable options but didn’t generate warnings with my sample data.

For simple situations, Data:HexDump seems a suitable choice at 4k. Even you want control over the output, and there is lots of control offered, Data::HexDumper does it in only 13.6K.

Here’s sample output for both, with default options for Data::HexDumper.

Data::Hexdumper

1
2
3
4
5
6
  0x0000 : B4 2D 81 35 D0 16 23 4C F4 ED 23 64 60 AB FC 37 : .-.5..#L..#d`..7
  0x0010 : 0C CA 60 C7 3F A2 58 75 53 CD 78 DF D3 0C 16 18 : ..`.?.XuS.x.....
  0x0020 : B7 CD 92 95 A2 E1 54 3A F3 89 7A 88 89 28 05 4A : ......T:..z..(.J
  0x0030 : 1B 18 C9 2F 61 8D 49 83 E6 2B 18 96 A6 40 8F BA : .../a.I..+...@..
  0x0040 : 44 0D A4 02 1E D2 01 E3 9F 1C 63 47 94 80 9B 74 : D.........cG...t
  0x0050 : 0C D1 63 46 0A 89 B5 1F F3 A7 68 4C 14 1B E6 2E : ..cF......hL....

Data::HexDump

1
2
3
4
5
6
7
8
      00 01 02 03 04 05 06 07 - 08 09 0A 0B 0C 0D 0E 0F  0123456789ABCDEF

00000000  B4 2D 81 35 D0 16 23 4C - F4 ED 23 64 60 AB FC 37  .-.5..#L..#d`..7
00000010  0C CA 60 C7 3F A2 58 75 - 53 CD 78 DF D3 0C 16 18  ..`.?.XuS.x.....
00000020  B7 CD 92 95 A2 E1 54 3A - F3 89 7A 88 89 28 05 4A  ......T:..z..(.J
00000030  1B 18 C9 2F 61 8D 49 83 - E6 2B 18 96 A6 40 8F BA  .../a.I..+...@..
00000040  44 0D A4 02 1E D2 01 E3 - 9F 1C 63 47 94 80 9B 74  D.........cG...t
00000050  0C D1 63 46 0A 89 B5 1F - F3 A7 68 4C 14 1B E6 2E  ..cF......hL....

Alfred 2 Workflow for Audio Device Selection

Alfred V2 has a great new feature called workflows. I’ve been playing with a few others wrote and had ported my old custom commands to workflows but was looking for something to really make use of their power.

Changing OSX Audio device from menubar Changing the selected audio output device was just the thing to implement as a workflow. OS X has a nice shortcut for doing this without heading to system preferences. Option clicking the speaker in the menu bar well let you change the input or output device, but I wanted to do it easily from the keyboard.

The ‘Script Filter’ option under the input menu is the magic for this workflow. It allows a script to output a list of items for display and selection in the Alfred GUI. You get Alfred’s great predictive auto complete which makes selecting a device a breeze.

Selecting output audio device in Alfred

Output via Growl or Notification Center provides feedback of the completed action.

Growl confirmation

The workflow is pretty simple in Alfred’s workflow editor, it ends up looking like this:

Alfred work flow

Alfred makes sharing workflows insanely easy by throwing all the necessary files for each workflow in a dedicated directory. Right clicking a workflow in Alfred gives an export workflow option which creates a zip file of this directory with the ‘.alfredworkflow’ extension.

Grab SwitchAudio.alfredworkflow

The heavy lifting is done by switchaudio-osx by deweller on GitHub with a simple perl glue script to pass data back and forth between Alfred and switchaudio-osx.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env perl

use strict;
use warnings;

use FindBin;

my $action = shift || 'list';
my $direction = shift || 'output';

if ($action eq 'list') {
    my $devices = get_possibilities($direction);
    print qq{<?xml version="1.0"?>\n<items>\n};
    print output_device($_) for @$devices;
    print "</items>\n";
}

if ( $action eq 'set' ) {
    my $device = shift;
    print set_device($direction, $device) . "\n";
}

sub get_possibilities {
    my $direction = shift;
    my @devices;
    open( my $fh, '-|', $FindBin::Bin . '/SwitchAudioSource',
        '-at', $direction );
    while ( my $line = <$fh> ) {
        chomp $line;
        $line =~ s/ \(\Q$direction\E\)$//;
        push @devices, $line;
    }
    close $fh;
    return \@devices;
}

sub set_device {
    my ( $direction, $device ) = @_;
    open( my $fh, '-|', $FindBin::Bin . '/SwitchAudioSource',
        '-t', $direction, '-s', $device );
    my $output = join "\n", <$fh>;
    close $fh;
    return $output;
}

sub output_device {
    my $device = shift;
    ( my $device_no_space = $device ) =~ tr/ /_/;
    return qq{<item arg="$device" uid="$device_no_space"><title>$device</title><subtitle/><icon>icon.png</icon></item>\n};
}

Fair Trade Coffee Price Premium

Galloway Township considered a resolution last week that would support fair trade practices. The resolution was tabled after some brief discussion. The cost difference between fair trade and non fair trade items was brought up and I was curious how much of a difference there was.

The town currently gets most office supplies from WB Mason. I do not know if this includes coffee but at work we get our coffee from WB Mason so as any Geek would do, I made a spread sheet.

Direct Link to Google Docs Spreadsheet

There are only two fair trade items in this category but they are right in line with prices for non fair trade varieties. If you are already using one of the non fair trade blends from the two companies with a fair trade blend, there is 0 price difference.

This lack of price difference surprised me.

ZSH Completion Waiting Dots

ZSH has some pretty nice tab completion but some of the completion modules can make things slow when you have a high latency conection. This can be extra harsh for me as I have failure to complete beeps turned off so I’m not sure if it’s still trying to complete or if there were no matches. When reading some zshrc snippts on Stack Overflow I came accross my favorite new snippit.

This snippet from a paid nerd adds ‘…’ output during completion that is replaced with the completion or removed once the completion engine is finished.

Here’s the snippet:

1
2
3
4
5
6
7
8
# http://stackoverflow.com/a/844299
expand-or-complete-with-dots() {
  echo -n "\e[31m...\e[0m"
  zle expand-or-complete
  zle redisplay
}
zle -N expand-or-complete-with-dots
bindkey "^I" expand-or-complete-with-dots

Also, get yourself in the habbit of including a comment with a url for stuff like this. I once wanted to write a blog post about something I found somewhere and couldn’t remember where I found it online! I’ll never make that mistake again.

Syncing Zsh Named Directories

One of my most used features of zsh is named directories. I keep my dotfiles synced across multiple machines via Dropbox (referral link) and a Makefile that creates symlinks automagically.

My named directories start with ~ for my home directory so they need to exist for expansion to work properly and not output errors when I log in. When I first implemented syncing I added this check with a very simple test: