From d6c3aaae436471816667529a7d626de5e1dadc51 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Sat, 22 Nov 2025 20:51:32 +0000 Subject: [PATCH 1/7] feat: implement custom myCat command with wildcard support and line numbering --- .../cat/sample-files/myCat.js | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100755 implement-shell-tools/cat/sample-files/myCat.js diff --git a/implement-shell-tools/cat/sample-files/myCat.js b/implement-shell-tools/cat/sample-files/myCat.js new file mode 100755 index 00000000..280ceb68 --- /dev/null +++ b/implement-shell-tools/cat/sample-files/myCat.js @@ -0,0 +1,79 @@ +#!/usr/bin/env node +const { program } = require("commander"); +const fs = require("fs"); +const path = require("path"); + + + +function expandWildcard(pattern) { + const dir = path.dirname(pattern); + const base = path.basename(pattern); + + if (!base.includes("*")) return [pattern]; + + let files; + try { + files = fs.readdirSync(dir); + } catch (e) { + console.error(`cat: ${pattern}: No such directory`); + return []; + } + + const regex = new RegExp("^" + base.replace(/\*/g, ".*") + "$"); + + return files + .filter((f) => regex.test(f)) + .map((f) => path.join(dir, f)); +} + +function printFile(filename, options) { + let text; + try { + text = fs.readFileSync(filename, "utf-8"); + } catch { + console.error(`cat: ${filename}: No such file`); + return; + } + + const lines = text.split("\n"); + if (lines[lines.length - 1] === "") { + lines.pop(); + } + let counter = 1; + + lines.forEach((line) => { + if (options.numberAll) { + const paddingSize = 6; + console.log(`${String(counter).padStart(paddingSize)} ${line}`); + counter++; + } else if (options.numberNonempty) { + if (line.trim() === "") { + console.log(""); + } else { + console.log(`${String(counter).padStart(paddingSize)} ${line}`); + counter++; + } + } else { + console.log(line); + } + }); +} + +program + .name("mycat") + .description("A custom implementation of the cat command") + .argument("", "files or wildcard patterns") + .option("-n, --number-all", "number all lines") + .option("-b, --number-nonempty", "number non-empty lines") + .action((patterns, options) => { + let allFiles = []; + + patterns.forEach((p) => { + allFiles = allFiles.concat(expandWildcard(p)); + }); + + allFiles.forEach((file) => printFile(file, options)); + }); + +program.parse(); + From 2ab9eeaf20b31c938eb81516f9780f612d820a39 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Sat, 22 Nov 2025 21:03:56 +0000 Subject: [PATCH 2/7] feat: implement custom 'ls' command with support for hidden files (-a) --- implement-shell-tools/ls/sample-files/myLs.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100755 implement-shell-tools/ls/sample-files/myLs.js diff --git a/implement-shell-tools/ls/sample-files/myLs.js b/implement-shell-tools/ls/sample-files/myLs.js new file mode 100755 index 00000000..f6ece12a --- /dev/null +++ b/implement-shell-tools/ls/sample-files/myLs.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node +const { program } = require("commander"); +const fs = require("fs"); +const path = require("path"); + +function listDirectory(dir, options) { + try { + const stats = fs.statSync(dir); + + if (stats.isFile()) { + console.log(dir); + return; + } + } catch (e) { + console.error(`ls: cannot access '${dir}': No such file or directory`); + return; + } + + let entries; + + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch (e) { + console.error(`ls: cannot access '${dir}': No such file or directory`); + return; + } + + let names = entries.map(e => e.name); + + if (options.all) { + names.unshift(".", ".."); + } else { + names = names.filter(name => !name.startsWith(".")); + } + + names.sort(); + names.forEach(name => console.log(name)); +} + +program + .name("myls") + .description("Custom implementation of ls") + .option("-1", "list one file per line (default in our version)") + .option("-a, --all", "include hidden files") + .argument("[dir]", "directory to list", ".") + .action((dir, options) => { + listDirectory(dir, options); + }); + +program.parse(); \ No newline at end of file From da8b88e2f0a959b3c0fbe5fe0e948dc2ac649263 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Sat, 22 Nov 2025 21:27:06 +0000 Subject: [PATCH 3/7] feat(myWc): complete custom wc implementation with full options and formatting --- implement-shell-tools/wc/sample-files/myWc.js | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100755 implement-shell-tools/wc/sample-files/myWc.js diff --git a/implement-shell-tools/wc/sample-files/myWc.js b/implement-shell-tools/wc/sample-files/myWc.js new file mode 100755 index 00000000..2dd81575 --- /dev/null +++ b/implement-shell-tools/wc/sample-files/myWc.js @@ -0,0 +1,97 @@ +#!/usr/bin/env node +const { program } = require("commander"); +const fs = require("fs"); +const path = require("path"); + +function expandWildcard(pattern) { + const dir = path.dirname(pattern); + const base = path.basename(pattern); + + if (!base.includes("*")) return [pattern]; + + let files; + try { + files = fs.readdirSync(dir); + } catch { + console.error(`wc: ${pattern}: No such directory`); + return []; + } + + const regex = new RegExp("^" + base.replace(/\*/g, ".*") + "$"); + return files + .filter(f => regex.test(f)) + .map(f => path.join(dir, f)); +} + +function countLines(text) { + if (text === "") return 0; + const matches = text.match(/\n/g) || []; + return text.endsWith("\n") ? matches.length : matches.length + 1; +} + +function countWords(text) { + return text.split(/\s+/).filter(Boolean).length; +} + +function countChars(text) { + return Buffer.byteLength(text, "utf-8"); +} + +function wcFile(filename, options) { + let text; + try { + text = fs.readFileSync(filename, "utf-8"); + } catch { + console.error(`wc: ${filename}: No such file`); + return null; + } + + const lineCount = countLines(text); + const wordCount = countWords(text); + const charCount = countChars(text); + + let output; + const paddingSize = 7; + if (options.lines && !options.words && !options.chars) output = `${lineCount} ${filename}`; + else if (options.words && !options.lines && !options.chars) output = `${wordCount} ${filename}`; + else if (options.chars && !options.lines && !options.words) output = `${charCount} ${filename}`; + else output = `${String(lineCount).padStart(paddingSize)} ${String(wordCount).padStart(paddingSize)} ${String(charCount).padStart(paddingSize)} ${filename}`; + console.log(output); + + return { lines: lineCount, words: wordCount, chars: charCount }; +} + +program + .name("mywc") + .description("Custom implementation of wc") + .option("-l, --lines", "count lines") + .option("-w, --words", "count words") + .option("-c, --chars", "count characters") + .argument("", "files or wildcard patterns") + .action((patterns, options) => { + let allFiles = []; + patterns.forEach(p => allFiles = allFiles.concat(expandWildcard(p))); + + let totalLines = 0, totalWords = 0, totalChars = 0; + + allFiles.forEach(file => { + const result = wcFile(file, options); + if (result) { + totalLines += result.lines; + totalWords += result.words; + totalChars += result.chars; + } + }); + const paddingSize = 7; + if (allFiles.length > 1) { + if (options.lines && !options.words && !options.chars) console.log(`${totalLines} total`); + else if (options.words && !options.lines && !options.chars) console.log(`${totalWords} total`); + else if (options.chars && !options.lines && !options.words) console.log(`${totalChars} total`); + else console.log( + `${String(totalLines).padStart(paddingSize)} ` + + `${String(totalWords).padStart(paddingSize)} ` + + `${String(totalChars).padStart(paddingSize)} total`); + } + }); + +program.parse(); From b476ec74a045102b663ac32e6d17fddcddc7de41 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Tue, 23 Dec 2025 20:48:07 +0000 Subject: [PATCH 4/7] Fix -b option and remove duplicated print logic --- .../cat/sample-files/myCat.js | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/implement-shell-tools/cat/sample-files/myCat.js b/implement-shell-tools/cat/sample-files/myCat.js index 280ceb68..9073b545 100755 --- a/implement-shell-tools/cat/sample-files/myCat.js +++ b/implement-shell-tools/cat/sample-files/myCat.js @@ -3,8 +3,6 @@ const { program } = require("commander"); const fs = require("fs"); const path = require("path"); - - function expandWildcard(pattern) { const dir = path.dirname(pattern); const base = path.basename(pattern); @@ -14,7 +12,7 @@ function expandWildcard(pattern) { let files; try { files = fs.readdirSync(dir); - } catch (e) { + } catch { console.error(`cat: ${pattern}: No such directory`); return []; } @@ -36,23 +34,23 @@ function printFile(filename, options) { } const lines = text.split("\n"); - if (lines[lines.length - 1] === "") { - lines.pop(); - } + if (lines[lines.length - 1] === "") lines.pop(); + let counter = 1; + const paddingSize = 6; lines.forEach((line) => { - if (options.numberAll) { - const paddingSize = 6; - console.log(`${String(counter).padStart(paddingSize)} ${line}`); + const isEmpty = line.trim() === ""; + + const shouldNumber = + options.numberAll || + (options.numberNonempty && !isEmpty); + + if (shouldNumber) { + console.log( + `${String(counter).padStart(paddingSize)} ${line}` + ); counter++; - } else if (options.numberNonempty) { - if (line.trim() === "") { - console.log(""); - } else { - console.log(`${String(counter).padStart(paddingSize)} ${line}`); - counter++; - } } else { console.log(line); } @@ -76,4 +74,3 @@ program }); program.parse(); - From bc841114223df0856e80f247f505ecb2c3d75e89 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Tue, 23 Dec 2025 22:54:17 +0000 Subject: [PATCH 5/7] fix(ls): make -1 option required instead of default --- implement-shell-tools/ls/sample-files/myLs.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/ls/sample-files/myLs.js b/implement-shell-tools/ls/sample-files/myLs.js index f6ece12a..9f011c19 100755 --- a/implement-shell-tools/ls/sample-files/myLs.js +++ b/implement-shell-tools/ls/sample-files/myLs.js @@ -40,7 +40,8 @@ function listDirectory(dir, options) { program .name("myls") .description("Custom implementation of ls") - .option("-1", "list one file per line (default in our version)") + .option("-1", "list one file per line (required)") + .option("-a, --all", "include hidden files") .argument("[dir]", "directory to list", ".") .action((dir, options) => { From 50ae619fef82e4a81ce0d18b4742f21a5be759d7 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Tue, 23 Dec 2025 22:55:24 +0000 Subject: [PATCH 6/7] fix(wc): apply consistent padding for all output modes and always right-align counts to match wc output --- implement-shell-tools/wc/sample-files/myWc.js | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/implement-shell-tools/wc/sample-files/myWc.js b/implement-shell-tools/wc/sample-files/myWc.js index 2dd81575..4c90111f 100755 --- a/implement-shell-tools/wc/sample-files/myWc.js +++ b/implement-shell-tools/wc/sample-files/myWc.js @@ -37,6 +37,24 @@ function countChars(text) { return Buffer.byteLength(text, "utf-8"); } +function formatOutput({ lines, words, chars }, options, label) { + const paddingSize = 7; + + const paddedLines = String(lines).padStart(paddingSize); + const paddedWords = String(words).padStart(paddingSize); + const paddedChars = String(chars).padStart(paddingSize); + + const onlyLines = options.lines && !options.words && !options.chars; + const onlyWords = options.words && !options.lines && !options.chars; + const onlyChars = options.chars && !options.lines && !options.words; + + if (onlyLines) return `${paddedLines} ${label}`; + if (onlyWords) return `${paddedWords} ${label}`; + if (onlyChars) return `${paddedChars} ${label}`; + + return `${paddedLines} ${paddedWords} ${paddedChars} ${label}`; +} + function wcFile(filename, options) { let text; try { @@ -46,19 +64,14 @@ function wcFile(filename, options) { return null; } - const lineCount = countLines(text); - const wordCount = countWords(text); - const charCount = countChars(text); - - let output; - const paddingSize = 7; - if (options.lines && !options.words && !options.chars) output = `${lineCount} ${filename}`; - else if (options.words && !options.lines && !options.chars) output = `${wordCount} ${filename}`; - else if (options.chars && !options.lines && !options.words) output = `${charCount} ${filename}`; - else output = `${String(lineCount).padStart(paddingSize)} ${String(wordCount).padStart(paddingSize)} ${String(charCount).padStart(paddingSize)} ${filename}`; - console.log(output); + const counts = { + lines: countLines(text), + words: countWords(text), + chars: countChars(text), + }; - return { lines: lineCount, words: wordCount, chars: charCount }; + console.log(formatOutput(counts, options, filename)); + return counts; } program @@ -70,9 +83,13 @@ program .argument("", "files or wildcard patterns") .action((patterns, options) => { let allFiles = []; - patterns.forEach(p => allFiles = allFiles.concat(expandWildcard(p))); + patterns.forEach(p => { + allFiles = allFiles.concat(expandWildcard(p)); + }); - let totalLines = 0, totalWords = 0, totalChars = 0; + let totalLines = 0; + let totalWords = 0; + let totalChars = 0; allFiles.forEach(file => { const result = wcFile(file, options); @@ -82,15 +99,15 @@ program totalChars += result.chars; } }); - const paddingSize = 7; + if (allFiles.length > 1) { - if (options.lines && !options.words && !options.chars) console.log(`${totalLines} total`); - else if (options.words && !options.lines && !options.chars) console.log(`${totalWords} total`); - else if (options.chars && !options.lines && !options.words) console.log(`${totalChars} total`); - else console.log( - `${String(totalLines).padStart(paddingSize)} ` + - `${String(totalWords).padStart(paddingSize)} ` + - `${String(totalChars).padStart(paddingSize)} total`); + console.log( + formatOutput( + { lines: totalLines, words: totalWords, chars: totalChars }, + options, + "total" + ) + ); } }); From 4787a6b9eee24f92a40ebe2f0d4fbabb1ef30065 Mon Sep 17 00:00:00 2001 From: RahwaZeslusHaile Date: Sat, 17 Jan 2026 07:11:24 +0000 Subject: [PATCH 7/7] Fix ls implementation: handle -1 flag correctly --- implement-shell-tools/ls/sample-files/myLs.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/implement-shell-tools/ls/sample-files/myLs.js b/implement-shell-tools/ls/sample-files/myLs.js index 9f011c19..b92594b6 100755 --- a/implement-shell-tools/ls/sample-files/myLs.js +++ b/implement-shell-tools/ls/sample-files/myLs.js @@ -4,12 +4,12 @@ const fs = require("fs"); const path = require("path"); function listDirectory(dir, options) { + let stats; try { - const stats = fs.statSync(dir); - + stats = fs.statSync(dir); if (stats.isFile()) { console.log(dir); - return; + return; } } catch (e) { console.error(`ls: cannot access '${dir}': No such file or directory`); @@ -17,7 +17,6 @@ function listDirectory(dir, options) { } let entries; - try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (e) { @@ -34,18 +33,21 @@ function listDirectory(dir, options) { } names.sort(); - names.forEach(name => console.log(name)); + if (options.one) { + names.forEach(name => console.log(name)); + } else { + console.log(names.join(" ")); + } } program .name("myls") .description("Custom implementation of ls") - .option("-1", "list one file per line (required)") - - .option("-a, --all", "include hidden files") + .option("-1, --one", "list one file per line ") + .option("-a, --all", "include hidden files and . and ..") .argument("[dir]", "directory to list", ".") .action((dir, options) => { listDirectory(dir, options); }); -program.parse(); \ No newline at end of file +program.parse();