Command line and Node basics
Command line arguments
You can get all the command line arguments you type in from the process.argv
array. However, this stores all the command line arguments.
node index.js
is itself two command line arguments:
- The first argument will always be the path to the node binary
- The second argument will always be the filepath of the file node is running
- Any additional arguments are ones you pass in.
So to parse any arguments you pass in, you have to start looking from the 2nd element onward in process.argv
.
Streams
Streams are a way of handling data in node in a way that is memory-efficient. It allows you to stream data, never having it all in memory. This is useful for handling large files, or large amounts of data.
import fs from "fs";
const readableStream = fs.createReadStream("file.txt");
const writableStream = fs.createWriteStream("file2.txt");
readableStream
.pipe(writableStream)
.on("finish", () => console.log("Done!"))
.on("error", (error) => console.log(error.message));
File System
Filepath manipulation
// Some important paths
process.cwd(); // Absolute path of the current working directory.
__filename; // Absolute path of the file that holds the current code.
__dirname; // Absolute path of the directory that holds __filename.
os.homedir(); // The user's home directory.
const path = require("path");
path.sep; // Either "/" or "\" depending on your OS
// The path module has simple parsing functions
let p = "src/pkg/test.js"; // An example path
path.basename(p); // => "test.js"
path.extname(p); // => ".js"
path.dirname(p); // => "src/pkg"
path.basename(path.dirname(p)); // => "pkg"
path.dirname(path.dirname(p)); // => "src"
// normalize() cleans up paths:
path.normalize("a/b/c/../d/"); // => "a/b/d/": handles ../ segments
path.normalize("a/./b"); // => "a/b": strips "./" segments
path.normalize("//a//b//"); // => "/a/b/": removes duplicate /
// join() combines path segments, adding separators, then normalizes
path.join("src", "pkg", "t.js"); // => "src/pkg/t.js"
// resolve() takes one or more path segments and returns an absolute
// path. It starts with the last argument and works backward, stopping
// when it has built an absolute path or resolving against process.cwd().
path.resolve(); // => process.cwd()
path.resolve("t.js"); // => path.join(process.cwd(), "t.js")
path.resolve("/tmp", "t.js"); // => "/tmp/t.js"
path.resolve("/a", "/b", "t.js"); // => "/b/t.js"
Important paths
process.cwd()
: Absolute path of the current working directory.__filename
: Absolute path of the file that holds the current code. Right now this is the absolut path of07-command-line.md
__dirname
: Absolute path of the directory that the current file lives inos.homedir()
: The user's home directory.path.sep
: Either "/" or "" depending on your OS, used for path separators.
Path methods
path.basename(filepath)
: Returns the basename (filename) of the path, like json.txtpath.extname(filepath)
: Returns the extension of the path, like.txt
path.dirname(filepath)
: Returns the directory name of the path, like./src/pkg
path.resolve(filepath)
: Returns the absolute path of the given filepath
Get file info
The fs.statSync()
method takes in a filepath and returns information about that file or folder, returning a stats
object. The stats
object has the following properties and methods:
stats.isFile()
: Returns true if the path is a filestats.isDirectory()
: Returns true if the path is a directorystats.size
: Returns the size of the file in bytes
const fs = require("fs");
let stats = fs.statSync("book/ch15.md");
stats.isFile(); // => true: this is an ordinary file
stats.isDirectory(); // => false: it is not a directory
stats.size; // file size in bytes
__dirname
in es6 modules
When using es6 modules in node by change the "type"
key in the package json to "module"
, you no longer have access to the __dirname
variable. To get the current directory, instead you have to do code like this:
import { fileURLToPath } from "url";
import { dirname, join } from "path";
// import.meta.url is the URL version of the filepath of the current file
const currentFileURL = import.meta.url;
// 1. convert import.meta.url to a static filepath
// 2. get the directory path of the directory that houses that filepath
const currentDir = dirname(fileURLToPath(currentFileURL));
// you now have access to the current directory as you please
const DB_FILE = join(currentDir, "db.json");
Making a CLI with commander
Child processes
The child_process
module in node allows you to run command line tools from within your nodeJS program, like cd
, ls
, and much more.
You can import it like so:
import * as child_process from "child_process";
ExecSync
The child_process.execSync(command, options)
method takes in a string command as its first argument, an an object of options as its second.
This method invokes a unix shell and runs the command in that shell, returning the standard output of the command as a string. If the command fails, it throws an error.
import * as child_process from "child_process";
async function runLinuxCommandLevel1() {
try {
// run the ls -l . command and store stdout in the output variable
let output = child_process.execSync("ls -l .", {
encoding: "utf8",
});
console.log(output);
} catch (err) {
// if the command fails, catch the error and log it
console.log(err);
}
}
ExecFileSync
For a more performant way of running linux commands, we can use the child_process.execFileSync(command, args, options)
method. This method takes in a command as its first argument, an array of arguments and options as its second, and an object of options as its third.
You can also execute scripts you wrote yourself with this.
- First argument: the file/script or command to run
- Second argument: a list of command line options to pass in
- Third argument: options like encoding
It's more perfomant because it doesn't spawn a shell, but instead runs the command directly.
async function runLinuxCommandLevel2() {
try {
let output = child_process.execFileSync("ls", ["-l", "."], {
encoding: "utf8",
});
console.log(output);
} catch (err) {
console.log(err);
}
}
Spawn
With the child_process.spawn()
method, we can have a more asynchronous approach where we listen to the stdout and stderr streams from executing a command.
const commandListener = spawn(command, [option1, option2, ...], options)
Like the other methods, here are the arguments in order:
- First argument: the file/script or command to run
- Second argument: a list of command line options to pass in
- Third argument: options like encoding
const { spawn } = require("child_process");
// runs this command: identify -verbose bidengoeshard.png
const identify = spawn("identify", ["-verbose", "bidengoeshard.png"]);
// handles the stdout stream
identify.stdout.on("data", (data: string) => {
// prints out the output in the command line from running the identify command
console.log(`stdout:\n ${data}`);
});
// handles the stderr stream
identify.stderr.on("data", (data: string) => {
// prints out the error in the command line, if any, when running the identify command
console.log(`stderr:\n ${data}`);
});
identify.on("exit", (code: number) => {
console.log(`child process exited with code ${code}`);
});
PM2
The pm2
command line tool which you can install with npm i -g pm2
allows you to run node programs as daemon threads, meaning that the will continue to run in the background perpetually until you yourself tell them to stop executing.
pm2 start my-node-program.js
PM2 commands
pm2 start <file>
: starts the node program as a daemon threadpm2 stop <file>
: stops the node programpm2 restart <file>
: restarts the node programpm2 delete <file>
: deletes the node program from the list of programs that pm2 is managingpm2 list
: lists all the running pm2 programs
PM2 options
-l <logfile>
: outputs logs to the specified log file-o <logfile>
: Log stdout from the script to the specified output file-e <logfile>
: Log stderr from the script to the specified error file--watch
: Watch for changes and restart the application-n <name>
: Set the name of the application in pm2
Here is an example with all these options put together:
pm2 start -l forever.log -o out.log -e err.log -n app_name index.js --watch