path

Dirname Does Not Follow Symlink

Node’s global object __dirname returns the name of the directory that the currently executing script resides in. However, if a directory is symbolic linked or a symlink, it still return the original path.

For example, the following directory structure:

1
2
3
4
5
6
7
8
/tmp/dirname/
├── foo
│ └── app.js
└── lib
├── bar -> ../foo
└── util.js
3 directories, 2 files

The app.js contains the following line:

1
console.log(__dirname);

Run the script:

1
2
$ cd /tmp/dirname/foo && node app.js
/tmp/dirname/foo

You get the directory the app.js script is residing it. But if you do the same from the symlinked directory:

1
2
$ cd /tmp/dirname/lib/bar && node app.js
/tmp/dirname/foo

Well, you get the same answer, even the current working directory is different:

1
2
$ cd /tmp/dirname/lib/bar && pwd
/tmp/dirname/lib/bar

The return of __dirname does not follow symbolic link.

So, have to be extra careful when requiring files that is symbolic linked. If you think you are in lib/bar directory and try to require the util.js script in app.js:

1
2
3
var path = require('path');
console.log(require('../util'));
console.log(require(path.join(__dirname, '..', 'util')));

Both statements will throw module not found error. Path join does not help either.

The best practice is to avoid symlinks and relative directory requiring. Instead, set up required as modules, and install them in node_modules directory, then they will become top-level modules.

Difference between require and fs in Loading Files

Node has a simple module loading system by using require.

A module prefixed with '/' is an absolute path to the file. For example, require('/home/marco/foo.js') will load the file at /home/marco/foo.js.

A module prefixed with './' is relative to the file calling require(). That is, circle.js must be in the same directory as foo.js for require('./circle') to find it.

Without a leading '/' or './' to indicate a file, the module is either a “core module” or is loaded from a node_modules folder.

http://nodejs.org/api/modules.html#modules_file_modules

We can also use it to load JSON files by simply specifying the file path. File path can be relative or absolute. Giving the following file structure:

1
2
3
4
5
6
.
├── app.js
├── data
│ └── file.json
└── lib
└── util.js

Let’s say the root path of the application is /home/marco. Use relative path from the file lib/util.js:

1
var json = require('../data/file.json');

This will load the JSON content from data/file.json, because the require system knows that the file is relative to the calling file lib/util.js. But when using file system functions, relative path is not treat the same way:

Relative path to filename can be used, remember however that this path will be relative to process.cwd(). - File System

process.cwd() returns the current working directory, not the root directory of the application nor the directory of the calling file. For example:

1
var data = fs.readFileSync('../data/file.json');

If your current working directory is /home/marco/myapp, the application root directory, you will get the following error message:

1
2
3
4
5
6
fs.js:410
return binding.open(pathModule._makeLong(path), stringToFlags(flags), mode);
^
Error: ENOENT, no such file or directory '../data/file.json'
at Object.fs.openSync (fs.js:410:18)
at Object.fs.readFileSync (fs.js:276:15)

Because process.cwd() is /home/marco/myapp, and reading file relative to this directory with ../data/file.json, Node will expect file path to be /home/marco/data/file.json. Therefore, the error is thrown.

The difference between require and fs in loading files is that if the relative path is used in require, it is relative to the file calling the require. If the relative path is used in fs, it is relative to process.cwd().

To fix the error in file system functions, we should always use absolute path. One thing we can do is:

1
var data = fs.readFileSync(__dirname + '/../data/file.json');

__dirname is “the name of the directory that the currently executing script is resides in” [dirname], and it has no trailing slash. The the structure looks odd, even though it works, but it might not work in all systems.

The correct solution here is to use path.join from Path:

1
var data = fs.readFileSync(path.join(__dirname, '../data/file.json'));

The path module “contains utilities for handling and transforming file paths”. The path.join function will call path.normalize to “normalize a string path, taking care of .. and . paths” [normalize], also see function normalizeArray in https://github.com/joyent/node/blob/master/lib/path.js on how the paths are normalized.

In conclusion:

1
2
3
4
5
6
// Use `require` to load JSON file.
var json = require('../data/file.json');
// Use `fs` to load JSON file.
var data = fs.readFileSync(path.join(__dirname, '../data/file.json'));
var json = JSON.parse(data.toString());

Defining Correct Environment for CoffeeScript in Cron

I have written a bunch of CoffeeScript scripts that I would like to run them regularly. Cron is certainly the right choice in many situation.

The default executable path recognized by cron is limited:

* * * * * echo $PATH > /tmp/log/cron.log

This cron job will recognize the following path:

/usr/bin:/bin

However the default executable path for Node.js is usually at:

$ which node
/usr/local/bin/node

Therefore, if you attempt to run a cron job:

* * * * * node -v > /tmp/log/cron.log 2> /tmp/log/cron.err.log

It will return an error, where the executable is not found:

bin/sh: 1: node: not found

The easiest way to solve it is by specifying the fully qualified path:

* * * * * /usr/local/bin/node -v > /tmp/log/cron.log

To ensure the process is installed, we can check the file existence and permission before launching it:

* * * * * test -x /usr/local/bin/node && /usr/local/bin/node -v

But when working with CoffeeScript, the same method does not working:

* * * * * /usr/local/bin/coffee -v 2> /tmp/log/cron.err.log

Because CoffeeScript binary (coffee) use the following format:

#!/usr/bin/env node

Which does not specify fully qualified path on the shebang line, and will result in:

/usr/bin/env: node: No such file or directory

We can compile CoffeeScript into JavaScript and use fully qualified node executable. But this is not necessary. Since we are using Debian based system, which uses Vixie cron. It allows environment variables to be defined. We can make Cron aware of the custom path in environment variable:

* * * * * PATH=$PATH:/usr/local/bin coffee -v > /tmp/log/cron.log

Now the executable path include the one where both node and coffee commands reside.

We can also move it to its own line. But Cron is not shell, it does not expand the variable, so you have to specify all paths:

# Error: `PATH=$PATH:/usr/local/bin`
PATH=/usr/bin:/bin:/usr/local/bin
* * * * * coffee -v > /tmp/log/cron.log

The good practice is to set environment variables in a script:

1
2
3
4
5
6
#!/usr/bin/env bash
#
# This script is intended to be run as a cron job.
PATH=$PATH:/usr/local/bin
coffee -v

and run the script as a cron job:

* * * * * /path/to/script > /tmp/log/cron.log 2>> /tmp/log/cron.err.log

Set Filename Path with process.cwd()

When working with file system in Node, you will need a fully qualified filename to do things like reading the content of a file:

require('fs').readFile(filename, function (err, data) {});

If you have the following directory structure:

.
├── app.js
├── data
│   └── file.txt
└── lib
    └── reader.js

There are two ways to read the content of data/file.txt from lib/reader.js:

Use the current directory of the script __dirname:

filename = __dirname + '/../data/file.txt';

or with path.join to normalize the resulting path:

filename = path.join(__dirname, '../data/file.txt');

The second method is to use the current working directory of the process process.cwd():

filename = process.cwd() + '/data/file.txt';

The second approach is more portable. If you move lib/reader.js to lib/utils/reader.js, no code change will be needed. But make sure the directory of the Node process is the application root directory where node app.js is being issued.