From de68e2e88b8cd4a1fe2f3d114482c3e31d37a6d1 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Fri, 21 Nov 2025 11:17:38 +0000 Subject: [PATCH 01/11] Implementing cat with process.argv-node feature to access what user types in terminal. --- implement-shell-tools/cat/cat.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 implement-shell-tools/cat/cat.js diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 00000000..cb19a15a --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,13 @@ +import process from "node:process" +import {promises as fs} from "node:fs" + + +const argv = process.argv.slice(2) +if (argv.length != 1) { + console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); + process.exit(1); +} + +const path = argv[0] +const content = await fs.readFile(path, "utf-8") +console.log(content) From fdbd244648d35aea74721f2edc89c76cfb5454f4 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Fri, 21 Nov 2025 14:42:06 +0000 Subject: [PATCH 02/11] Adding gitignore and package.json for installing dependencies --- implement-shell-tools/.gitignore | 5 +++++ implement-shell-tools/package.json | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 implement-shell-tools/.gitignore create mode 100644 implement-shell-tools/package.json diff --git a/implement-shell-tools/.gitignore b/implement-shell-tools/.gitignore new file mode 100644 index 00000000..84e210b2 --- /dev/null +++ b/implement-shell-tools/.gitignore @@ -0,0 +1,5 @@ +# Node modules +node_modules/ + +# Package lock file (optional) +package-lock.json \ No newline at end of file diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 00000000..aff57833 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "commander": "^14.0.2" + } +} From 007ee5411e94cf2b19fca906170e190c0ec5e780 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Fri, 21 Nov 2025 14:42:52 +0000 Subject: [PATCH 03/11] Adding implementation to count number of lines --- implement-shell-tools/cat/cat.js | 13 ---------- implement-shell-tools/cat/mycat.js | 38 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 13 deletions(-) delete mode 100644 implement-shell-tools/cat/cat.js create mode 100644 implement-shell-tools/cat/mycat.js diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js deleted file mode 100644 index cb19a15a..00000000 --- a/implement-shell-tools/cat/cat.js +++ /dev/null @@ -1,13 +0,0 @@ -import process from "node:process" -import {promises as fs} from "node:fs" - - -const argv = process.argv.slice(2) -if (argv.length != 1) { - console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); - process.exit(1); -} - -const path = argv[0] -const content = await fs.readFile(path, "utf-8") -console.log(content) diff --git a/implement-shell-tools/cat/mycat.js b/implement-shell-tools/cat/mycat.js new file mode 100644 index 00000000..4a1de3d1 --- /dev/null +++ b/implement-shell-tools/cat/mycat.js @@ -0,0 +1,38 @@ +import { program } from "commander"; +import {promises as fs} from "node:fs" + + + + //configuring the program here to run flags. + +program + .name("cat") + .description("Concatenate and print files") + .option("-n, --number", "Number all output lines") + .option("-b, --number-nonblank", "Number non-blank output lines") + .argument("", "File paths to display") + .parse(process.argv);//Parse command line arguments (reads process.argv and interprets it) + +const options = program.opts(); +const files = program.args; // Array of file paths passed as arguments + +let lineNumber = 1; // shared across all files, like GNU cat -n) + +for (const file of files) { + const content = await fs.readFile(file, "utf-8"); + const lines = content.split("\n"); + + for (const line of lines) { + + if (options.number) { + console.log(`${lineNumber.toString().padStart(6)} ${line}`); // adding padding for formatting + lineNumber++; + } + else{ + console.log(line); + } + } + } + + + From 56e274077d0187d6f6e0195bcb217ef652383895 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Fri, 21 Nov 2025 14:59:54 +0000 Subject: [PATCH 04/11] Adding implementation for the b flag --- implement-shell-tools/cat/{mycat.js => file.js} | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) rename implement-shell-tools/cat/{mycat.js => file.js} (81%) diff --git a/implement-shell-tools/cat/mycat.js b/implement-shell-tools/cat/file.js similarity index 81% rename from implement-shell-tools/cat/mycat.js rename to implement-shell-tools/cat/file.js index 4a1de3d1..f578048e 100644 --- a/implement-shell-tools/cat/mycat.js +++ b/implement-shell-tools/cat/file.js @@ -28,7 +28,14 @@ for (const file of files) { console.log(`${lineNumber.toString().padStart(6)} ${line}`); // adding padding for formatting lineNumber++; } - else{ + else if (options.numberNonblank) { // -b + if (line.trim() === "") { + console.log(line); + } else { + console.log(`${lineNumber.toString().padStart(6)} ${line}`); + lineNumber++; + } + } else { console.log(line); } } From 1bcd51349e7ba2cf54b01f28c44c17bea4c04560 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Fri, 21 Nov 2025 15:36:08 +0000 Subject: [PATCH 05/11] implementation for ls -1 flag. --- implement-shell-tools/ls/ls.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 implement-shell-tools/ls/ls.js diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 00000000..2f5cce05 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,23 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +//configuring +program + .name("ls") + .description("list directory contents") + .option("-1, --one", "Outputs are printed one entry per line") + .argument("[directory]", "Directory to list", "."); // "." means current directory + +//interpret the program +program.parse(); +const directory = program.args[0] || "."; //get dir arg- 1st arg in program.args array || if no arg default to current dir + + +const files = await fs.readdir(directory); //read the dir to get array of filenames + + + +//print each file on its own line +for (const file of files) { // Loop prints each individually on separate lines + console.log(file) +} \ No newline at end of file From e44078e4a105e024fb55f2d2ecd9b731d35d0794 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Fri, 21 Nov 2025 15:56:14 +0000 Subject: [PATCH 06/11] Adding implementation for -a flag --- implement-shell-tools/ls/ls.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 2f5cce05..fa854d4a 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -6,18 +6,27 @@ program .name("ls") .description("list directory contents") .option("-1, --one", "Outputs are printed one entry per line") + .option("-a, --all","Show all files including hidden files that start with a .") .argument("[directory]", "Directory to list", "."); // "." means current directory //interpret the program program.parse(); +const options = program.opts(); const directory = program.args[0] || "."; //get dir arg- 1st arg in program.args array || if no arg default to current dir -const files = await fs.readdir(directory); //read the dir to get array of filenames +let files = await fs.readdir(directory); //read the dir to get array of filenames +if (!options.all) { //Handle -a (include hidden files) + files = files.filter(name => !name.startsWith(".")); +} -//print each file on its own line -for (const file of files) { // Loop prints each individually on separate lines - console.log(file) +if (options.one) { // Print each file on its own line + for (const file of files) { + console.log(file); + } +} +else { + console.log(files.join(" "));// Default: join with spaces (like ls without -1) } \ No newline at end of file From 99aae232176cf4eee4deaf9d3f64ff99942d6355 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Sat, 22 Nov 2025 20:00:53 +0000 Subject: [PATCH 07/11] Refactoring code with helper functions and removing trailing extra lines. --- implement-shell-tools/cat/file.js | 53 +++++++++++++++++++------------ 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/implement-shell-tools/cat/file.js b/implement-shell-tools/cat/file.js index f578048e..53f9adb5 100644 --- a/implement-shell-tools/cat/file.js +++ b/implement-shell-tools/cat/file.js @@ -1,8 +1,9 @@ +//On macOS (BSD cat), the -n option resets numbering for each file, so multiple files start again at 1, +//when you run cat -n sample-files/*.txt +//This Node.js script mimics GNU cat, where -n continues numbering across all files instead of restarting. import { program } from "commander"; import {promises as fs} from "node:fs" - - //configuring the program here to run flags. program @@ -13,33 +14,43 @@ program .argument("", "File paths to display") .parse(process.argv);//Parse command line arguments (reads process.argv and interprets it) + +//Constants +const paddingWidth = 6; // width for line number padding + +// Helper function to format a line with numbering +function formatLine(line, lineNumber, numberAll, numberNonBlank) { + if (numberAll) { + return `${lineNumber.toString().padStart(paddingWidth)} ${line}`; + } + if (numberNonBlank) { + if (line.trim() === "") { + return line; // blank line, no number + } + return `${lineNumber.toString().padStart(paddingWidth)} ${line}`; + } + return line; // no numbering +} const options = program.opts(); const files = program.args; // Array of file paths passed as arguments -let lineNumber = 1; // shared across all files, like GNU cat -n) +let lineNumber = 1; // shared across all files for (const file of files) { const content = await fs.readFile(file, "utf-8"); - const lines = content.split("\n"); + let lines = content.split("\n"); + // Remove trailing empty line if file ends with newline + if (lines.length > 0 && lines[lines.length - 1] === "") { + lines.pop(); + } for (const line of lines) { - - if (options.number) { - console.log(`${lineNumber.toString().padStart(6)} ${line}`); // adding padding for formatting + const formatted = formatLine(line, lineNumber, options.number, options.numberNonblank); + console.log(formatted); + + // Increment line number if numbering applies + if (options.number || (options.numberNonblank && line.trim() !== "")) { lineNumber++; - } - else if (options.numberNonblank) { // -b - if (line.trim() === "") { - console.log(line); - } else { - console.log(`${lineNumber.toString().padStart(6)} ${line}`); - lineNumber++; - } - } else { - console.log(line); - } } } - - - +} \ No newline at end of file From e8c2041bff412e264365d2d312a0b872dbd86d99 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Sat, 22 Nov 2025 20:07:35 +0000 Subject: [PATCH 08/11] node implementation for wc --- implement-shell-tools/wc/wc.js | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 implement-shell-tools/wc/wc.js diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 00000000..339afa50 --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,61 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; + +//configuring +program + .name("wc") + .description("Print newline, word, and byte counts for each file") + .option("-l, --lines", "Print the newline counts") + .option("-w, --words", "Print the word counts") + .option("-c, --bytes", "Print the byte counts") + .argument("", "File paths to process") +//Interpret the program + program.parse() +const options = program.opts() +const files = program.args //Interpreting parsed data + + +//helper function to calculate all counts +async function getCounts(file){ + const content = await fs.readFile(file, "utf-8"); + const lineCount = content.split("\n").length -1;//split("\n") returns one more element than the actual number so length-1, + const wordCount = content.trim().split(/\s+/).length; + const byteCount = Buffer.byteLength(content, "utf-8"); //Calculate how many bytes the string uses in UTF-8 (important because some characters use more than 1 byte) + return { lineCount, wordCount, byteCount }; +} +//initiating totals +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; + +for (const file of files) { + const { lineCount, wordCount, byteCount } = await getCounts(file); + + totalLines += lineCount; + totalWords += wordCount; + totalBytes += byteCount; + +let output = ""; + if (options.lines) output += `${lineCount} `; + if (options.words) output += `${wordCount} `; + if (options.bytes) output += `${byteCount} `; + if (!options.lines && !options.words && !options.bytes) { + output += `${lineCount} ${wordCount} ${byteCount} `; + } + output += file; + console.log(output); +} + + + //Print totals if multiple files +if (files.length > 1) { + let totalOutput = ""; + if (options.lines) totalOutput += `${totalLines} `; + if (options.words) totalOutput += `${totalWords} `; + if (options.bytes) totalOutput += `${totalBytes} `; + if (!options.lines && !options.words && !options.bytes) { + totalOutput += `${totalLines} ${totalWords} ${totalBytes} `; + } + totalOutput += "total"; + console.log(totalOutput); +} \ No newline at end of file From 6152444ac953558ae15468006013d0d414a2605d Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Sat, 22 Nov 2025 20:23:07 +0000 Subject: [PATCH 09/11] Fix -a flag handling to include current and parent dir while running with node to mimic ls --- implement-shell-tools/ls/ls.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index fa854d4a..449835ae 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -17,8 +17,15 @@ const directory = program.args[0] || "."; //get dir arg- 1st arg in program.args let files = await fs.readdir(directory); //read the dir to get array of filenames - -if (!options.all) { //Handle -a (include hidden files) +//Handle -a (include hidden files) +// Node's fs.readdir() does not include the special directory entries "." (current dir) +// and ".." (parent dir). The real `ls -a` command shows them, so we add them manually here +// to match the behavior of `ls -a`. +if (options.all) { + + files.unshift(".."); + files.unshift("."); +} else { files = files.filter(name => !name.startsWith(".")); } From 9463024ea0babc2a563bc1ea7e2c4e855738ffc1 Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Sun, 21 Dec 2025 21:23:28 +0000 Subject: [PATCH 10/11] Removed the duplicated code for line formatting --- implement-shell-tools/cat/file.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/implement-shell-tools/cat/file.js b/implement-shell-tools/cat/file.js index 53f9adb5..15490496 100644 --- a/implement-shell-tools/cat/file.js +++ b/implement-shell-tools/cat/file.js @@ -20,17 +20,15 @@ const paddingWidth = 6; // width for line number padding // Helper function to format a line with numbering function formatLine(line, lineNumber, numberAll, numberNonBlank) { + const numbered =`${lineNumber.toString().padStart(paddingWidth)} ${line}`; if (numberAll) { - return `${lineNumber.toString().padStart(paddingWidth)} ${line}`; + return numbered } - if (numberNonBlank) { - if (line.trim() === "") { - return line; // blank line, no number - } - return `${lineNumber.toString().padStart(paddingWidth)} ${line}`; - } - return line; // no numbering -} + if (numberNonBlank) { + return line.trim() === "" ? line : numbered; + } + return line; + } const options = program.opts(); const files = program.args; // Array of file paths passed as arguments From 3de99680a5b684b97497b96106303ff15115134a Mon Sep 17 00:00:00 2001 From: Sara Tahir Date: Sun, 21 Dec 2025 21:49:17 +0000 Subject: [PATCH 11/11] Removing duplication of output code with a helper function. --- implement-shell-tools/wc/wc.js | 46 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 339afa50..fda14b91 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -23,7 +23,18 @@ async function getCounts(file){ const byteCount = Buffer.byteLength(content, "utf-8"); //Calculate how many bytes the string uses in UTF-8 (important because some characters use more than 1 byte) return { lineCount, wordCount, byteCount }; } -//initiating totals +//helper to remove duplicated output logic +function formatOutput(lines, words, bytes, filename, options) { + let formattedOutput = ""; + if (options.lines) formattedOutput += `${lines} `; + if (options.words) formattedOutput+= `${words} `; + if (options.bytes) formattedOutput += `${bytes} `; + if (!options.lines && !options.words && !options.bytes) { // default: print all three + formattedOutput += `${lines} ${words} ${bytes} `; + } + return formattedOutput + filename; + } + //Initiating totals let totalLines = 0; let totalWords = 0; let totalBytes = 0; @@ -35,27 +46,16 @@ for (const file of files) { totalWords += wordCount; totalBytes += byteCount; -let output = ""; - if (options.lines) output += `${lineCount} `; - if (options.words) output += `${wordCount} `; - if (options.bytes) output += `${byteCount} `; - if (!options.lines && !options.words && !options.bytes) { - output += `${lineCount} ${wordCount} ${byteCount} `; - } - output += file; - console.log(output); + const formatted = formatOutput(lineCount, wordCount, byteCount, file, options); + console.log(formatted); } - - - //Print totals if multiple files +//Print totals if multiple files if (files.length > 1) { - let totalOutput = ""; - if (options.lines) totalOutput += `${totalLines} `; - if (options.words) totalOutput += `${totalWords} `; - if (options.bytes) totalOutput += `${totalBytes} `; - if (!options.lines && !options.words && !options.bytes) { - totalOutput += `${totalLines} ${totalWords} ${totalBytes} `; - } - totalOutput += "total"; - console.log(totalOutput); -} \ No newline at end of file + const formattedTotals = formatOutput(totalLines, totalWords, totalBytes, "total", options); + console.log(formattedTotals); +} + + + + + \ No newline at end of file