Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
.venv
7 changes: 7 additions & 0 deletions implement-shell-tools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Ignore node_modules everywhere
**/node_modules/

#ignore logs, environment files.
*.log
.env
dist/
75 changes: 75 additions & 0 deletions implement-shell-tools/cat/cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { program } from "commander";
import { promises as fs } from "node:fs";
import process from "node:process";

program
.name("my-cat")
.description("Reads and prints one or more files, optionally numbering lines continuously")
.argument("<paths...>", "One or more file paths")
.option("-n, --number", "Number all output lines", false)
.option("-b, --number-nonblank", "Number non-empty output lines", false);

program.parse();

const filePaths = program.args;
const options = program.opts();

const numberNonBlank = options.numberNonblank;
const numberAll = options.number && !numberNonBlank;

(async () => {
let lineNumber = 1;

// calculate total lines if using -n
let totalLines = 0;
if (numberAll) {
for (const path of filePaths) {
try {
const content = await fs.readFile(path, "utf-8");
const lines = content.split("\n");

totalLines += content.endsWith("\n") ? lines.length - 1 : lines.length;
} catch (err) {
console.error(`Error reading file "${path}": ${err.message}`);
process.exit(1);
}
}
}

for (const path of filePaths) {
try {
const content = await fs.readFile(path, "utf-8");
const lines = content.split("\n");

if (numberNonBlank) {
const maxDigits = String(lines.filter(line => line.trim() !== '').length).length;

for (const line of lines) {
if (line.trim() === '') {
process.stdout.write("\n");
} else {
const numStr = String(lineNumber++).padStart(maxDigits, " ");
process.stdout.write(`${numStr}\t${line}\n`);
}
}
} else if (numberAll) {
const maxDigits = totalLines > 0 ? String(totalLines).length : 1;


for (const line of lines) {
const numStr = String(lineNumber++).padStart(maxDigits, " ");
process.stdout.write(`${numStr}\t${line}\n`);
}
} else {
// no numbering, just print file content as is
process.stdout.write(content);
if (!content.endsWith("\n")) {
process.stdout.write("\n"); // ensure newline after file if missing
}
}
} catch (err) {
console.error(`Error reading file "${path}": ${err.message}`);
process.exit(1);
}
}
})();
25 changes: 25 additions & 0 deletions implement-shell-tools/cat/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions implement-shell-tools/cat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "cat",
"version": "1.0.0",
"description": "You should already be familiar with the `cat` command line tool.",
"main": "cat.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"commander": "^14.0.0"
}
}
69 changes: 69 additions & 0 deletions implement-shell-tools/ls/ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { program } from "commander";
import { promises as fs } from "node:fs";
import path from "node:path";

program
.name("my-ls")
.description("List files in a directory (simplified ls implementation)")
.argument("[paths...]", "One or more file or directory paths")
.option("-l, --longList", "Long listing format", false)
.option("-a, --all", "Include hidden files", false);

program.parse();

// CHANGED: apply default value for paths here if none are provided
let filePaths = program.args.length > 0 ? program.args : ['.'];
const options = program.opts();

const showLong = options.longList;
const showAll = options.all;

// Helper function to convert mode to rwxrwxrwx string
function formatPermissions(mode, isDir) {
const typeChar = isDir ? 'd' : '-';
const perms = [0, 1, 2].map(i => {
const shift = 6 - i * 3;
const r = (mode >> shift) & 4 ? 'r' : '-';
const w = (mode >> shift) & 2 ? 'w' : '-';
const x = (mode >> shift) & 1 ? 'x' : '-';
return r + w + x;
}).join('');
return typeChar + perms;
}

(async () => {
for (const inputPath of filePaths) {
try {
const stat = await fs.stat(inputPath);

if (stat.isFile()) {
if (showLong) {
const perms = formatPermissions(stat.mode, false);
console.log(`${perms} ${stat.size.toString().padStart(6)} ${inputPath}`);
} else {
console.log(inputPath);
}
} else if (stat.isDirectory()) {
const entries = await fs.readdir(inputPath, { withFileTypes: true });

const filtered = showAll
? entries
: entries.filter((entry) => !entry.name.startsWith("."));

for (const entry of filtered) {
const fullPath = path.join(inputPath, entry.name);
const entryStat = await fs.stat(fullPath);
if (showLong) {
const perms = formatPermissions(entryStat.mode, entryStat.isDirectory());
console.log(`${perms} ${entryStat.size.toString().padStart(6)} ${entry.name}`);
} else {
console.log(entry.name);
}
}
}
} catch (err) {
console.error(`Error reading "${inputPath}": ${err.message}`);
process.exit(1);
}
}
})();
21 changes: 21 additions & 0 deletions implement-shell-tools/ls/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions implement-shell-tools/ls/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"dependencies": {
"commander": "^14.0.0"
},
"name": "ls",
"version": "1.0.0",
"description": "You should already be familiar with the `ls` command line tool.",
"main": "ls.js",
"type": "module",
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
21 changes: 21 additions & 0 deletions implement-shell-tools/wc/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions implement-shell-tools/wc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "module",
"dependencies": {
"commander": "^14.0.0"
}
}
74 changes: 74 additions & 0 deletions implement-shell-tools/wc/wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { program } from "commander";
import { promises as fs } from "node:fs";
import { stat } from "node:fs/promises";

program
.name("my-wc")
.description("Simplified implementation of wc")
.argument("[paths...]", "One or more file or directory paths")
.option("-l, --line", "Count lines")
.option("-w, --word", "Count words")
.option("-c, --character", "Count characters");

program.parse();

const filePaths = program.args.length > 0 ? program.args : ['.'];
const options = program.opts();

const countContent = (content) => {
const lines = content.split('\n').length;
const words = content.trim().split(/\s+/).filter(Boolean).length;
const characters = content.length;
return { lines, words, characters };
};

const total = {
lines: 0,
words: 0,
characters: 0
};

//Output formatting function
function formatOutput(counts, label, options) {
const { lines, words, characters } = counts;
const showAll = !options.line && !options.word && !options.character;

const parts = [];

if (options.line || showAll) parts.push(lines.toString().padStart(8));
if (options.word || showAll) parts.push(words.toString().padStart(8));
if (options.character || showAll) parts.push(characters.toString().padStart(8));

parts.push(label);
return parts.join(" ");
}

(async () => {
let fileCount = 0;

for (const inputPath of filePaths) {
try {
const stats = await stat(inputPath);
if (stats.isDirectory()) {
console.log(`${inputPath} is a directory. Skipping.`);
continue;
}

const content = await fs.readFile(inputPath, "utf-8");
const counts = countContent(content);

total.lines += counts.lines;
total.words += counts.words;
total.characters += counts.characters;
fileCount++;

console.log(formatOutput(counts, inputPath, options));
} catch (err) {
console.error(`Error reading "${inputPath}": ${err.message}`);
}
}

if (fileCount > 1) {
console.log(formatOutput(total, "total", options));
}
})();
2 changes: 2 additions & 0 deletions shell-pipelines/ls-grep/script-01.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ set -euo pipefail

# TODO: Write a command to output the names of the files in the sample-files directory whose name contains at least one upper case letter.
# Your output should contain 11 files.

ls sample-files | grep '[A-Z]'
3 changes: 3 additions & 0 deletions shell-pipelines/ls-grep/script-02.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ set -euo pipefail

# TODO: Write a command to output the names of the files in the sample-files directory whose name starts with an upper case letter.
# Your output should contain 10 files.

ls sample-files | grep '^[A-Z]'

2 changes: 2 additions & 0 deletions shell-pipelines/ls-grep/script-03.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ set -euo pipefail

# TODO: Write a command to output the names of the files in the sample-files directory whose name starts with an upper case letter and doesn't contain any other upper case letters.
# Your output should contain 7 files.

ls sample-files | grep -E '^[A-Z][^A-Z]*$'
2 changes: 2 additions & 0 deletions shell-pipelines/ls-grep/script-04.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ set -euo pipefail

# TODO: Write a command to count the number of files in the sample-files directory whose name starts with an upper case letter and doesn't contain any other upper case letters.
# Your output should be the number 7.

ls sample-files | grep -E '^[A-Z][^A-Z]*$' | wc -l
2 changes: 2 additions & 0 deletions shell-pipelines/sort-uniq-head-tail/script-01.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ set -euo pipefail
# The input for this script is the scores-table.txt file.
# TODO: Write a command to output scores-table.txt, with lines sorted by the person's name.
# The first line of your output should be "Ahmed London 1 10 4" (with no quotes). And the third line should be "Chandra Birmingham 12 6".

sort scores-table.txt
3 changes: 3 additions & 0 deletions shell-pipelines/sort-uniq-head-tail/script-02.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ set -euo pipefail
# The input for this script is the scores-table.txt file.
# TODO: Write a command to output scores-table.txt, with lines sorted by the person's first score, descending.
# The first line of your output should be "Basia London 22 9 6" (with no quotes).


sort -k3,3 -nr scores-table.txt
2 changes: 2 additions & 0 deletions shell-pipelines/sort-uniq-head-tail/script-03.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ set -euo pipefail
# Basia London 22 9 6
# Piotr Glasgow 15 2 25 11 8
# Chandra Birmingham 12 6

sort -k3,3 -nr scores-table.txt | head -n 3
2 changes: 2 additions & 0 deletions shell-pipelines/sort-uniq-head-tail/script-04.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ set -euo pipefail
# The input for this script is the scores-table.txt file.
# TODO: Write a command to output scores-table.txt, with shows the line for the player whose first score was the second highest.
# Your output should be: "Piotr Glasgow 15 2 25 11 8" (without quotes).

sort -k3,3 -nr scores-table.txt | head -n 2 | tail -n 1
Loading
Loading