Skip to content
45 changes: 45 additions & 0 deletions implement-shell-tools/cat/my_cat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { promises as fs } from 'node:fs';
import { argv } from 'node:process';

const args = argv.slice(2); // skip node and script name

let showLineNumb = false;
let showNoBlankNumb = false
const files = [];


args.forEach(arg => {
if (arg === '-n') {
showLineNumb = true;
} else if (arg === '-b') {
showNoBlankNumb = true;
}else {
files.push(arg);
}
});

if (files.length === 0) {
console.error('No input file specified');
process.exit(1);
}

// Global counters across all files
let counter = 1;

for (const file of files) {
try {
let content = await fs.readFile(file, 'utf-8');
const lines = content.split('\n');

if (showNoBlankNumb) { // Number only non-blank lines
content = lines.map(line =>
line.trim() === '' ? '' : `${counter++}\t${line}`
).join('\n');
} else if (showLineNumb) { // Number all lines
content = lines.map((line) => `${counter++}\t${line}`).join('\n');
}
console.log(content);
} catch (err) {
console.error(`Cannot access '${file}': ${err.message}`);
}
}
32 changes: 32 additions & 0 deletions implement-shell-tools/ls/my_ls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { readdirSync } from 'node:fs';
import { argv } from 'node:process';

const args = argv.slice(2); // skips node and script name

const flags = args.filter(arg => arg.startsWith('-'));
const operands = args.filter(arg => !arg.startsWith('-'));

let showAll = flags.includes('-a');
let onePerLine = flags.includes('-1');

if (operands.length === 0) operands.push('.'); // defaults to current dir

operands.forEach(dir => {
try {
let files = readdirSync(dir); // Reads the contents of the dir synchronously (readdirSync)

if (!showAll) {
files = files.filter(file => !file.startsWith('.'));
}

files.sort((a, b) => a.localeCompare(b));

if (onePerLine) {
files.forEach(file => console.log(file));
} else {
console.log(files.join(' '));
}
} catch (err) {
console.error(`ls: cannot access '${dir}': ${err.message}`);
}
});
96 changes: 96 additions & 0 deletions implement-shell-tools/wc/my_wc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { promises as fs } from 'node:fs';
import { argv } from 'node:process';

const args = argv.slice(2);

if (args.length === 0) {
console.error('Usage: node my_wc.mjs [-l] [-w] [-c] <file> [file2 ...]');
process.exit(1);
}

// Parse flags and files
const flags = {
l: false,
w: false,
c: false,
};

const files = [];

for (const arg of args) {
if (arg.startsWith('-') && arg.length > 1) {
for (const ch of arg.slice(1)) {
if (flags.hasOwnProperty(ch)) {
flags[ch] = true;
} else {
console.error(`Unknown option: -${ch}`);
process.exit(1);
}
}
} else {
files.push(arg);
}
}

if (files.length === 0) {
console.error('Error: no files specified.');
process.exit(1);
}

// If no flags specified, show all by default
const showLines = flags.l || (!flags.l && !flags.w && !flags.c);
const showWords = flags.w || (!flags.l && !flags.w && !flags.c);
const showBytes = flags.c || (!flags.l && !flags.w && !flags.c);

function pad(num, width = 8) {
return num.toString().padStart(width, ' ');
}

function formatOutput({ lines, words, bytes }, label) {
let output = '';
if (showLines) output += pad(lines);
if (showWords) output += pad(words);
if (showBytes) output += pad(bytes);
output += ` ${label}`;
return output;
}

async function countFile(file) {
try {
const content = await fs.readFile(file, 'utf-8');
const lines = content.split('\n').length - 1;
const words = content.trim().split(/\s+/).filter(Boolean).length;
const bytes = Buffer.byteLength(content, 'utf-8');

const counts = { lines, words, bytes };
console.log(formatOutput(counts, file));

return counts;
} catch (err) {
console.error(`Cannot access '${file}': ${err.message}`);
return null;
}
}

async function main() {
// Instead of .map, I can explicitly collect promises using forEach():
const promises = [];
files.forEach(file => {
promises.push(countFile(file));
});
const counts = await Promise.all(promises);

const validCounts = counts.filter(c => c !== null);

if (validCounts.length > 1) {
const totals = {
lines: validCounts.reduce((sum, c) => sum + c.lines, 0),
words: validCounts.reduce((sum, c) => sum + c.words, 0),
bytes: validCounts.reduce((sum, c) => sum + c.bytes, 0),
}

console.log(formatOutput(totals, 'total'));
}
}

main();