From 708e4cebc8aac3a1f5e1aa9a0c402397929eb9d0 Mon Sep 17 00:00:00 2001 From: Ebrahim Beiaty Date: Sat, 14 Mar 2026 14:32:41 +0000 Subject: [PATCH 1/4] implements-shell-tool --- implement-shell-tools/cat/cat.js | 76 ++++++++++++++++++++++++++++++++ implement-shell-tools/ls/ls.js | 52 ++++++++++++++++++++++ implement-shell-tools/wc/wc.js | 71 +++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 implement-shell-tools/cat/cat.js create mode 100644 implement-shell-tools/ls/ls.js create mode 100755 implement-shell-tools/wc/wc.js diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 000000000..2bdbfc039 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +const fs = require('fs'); +const path = require('path'); + +//shared line counter across all files(matches cat -n) +let globalLineCounter = 1; + +function printFile(filePath, options) { + try { + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n'); + + lines.forEach((line) => { + if(options.numberNonEmpty) { + //-b option: number non-empty lines + if(line.trim()) { + process.stdout.write( + `${String(globalLineCounter).padStart(6)}\t${line}\n` + ); + globalLineCounter++; + } else { + process.stdout.write('\n'); + } + } else if(options.numberAll) { + //-n option: number all lines + process.stdout.write( + `${String(globalLineCounter).padStart(6)}\t${line}\n` + ); + globalLineCounter++; + } else { + //default: just print the line + process.stdout.write(line + '\n'); + } + }); + + } catch (error) { + console.error(`Error reading file ${filePath}: ${error.message}`); + } +} + +function main() { + const args = process.argv.slice(2); + const options = { + numberNonEmpty: false, + numberAll: false, + }; + const filePatterns = []; + + args.forEach((arg) => { + if(arg === '-n') { + options.numberAll = true; + } else if(arg === '-b') { + options.numberNonEmpty = true; + } else { + filePatterns.push(arg); + } + }); + // -b takes precedence over -n + if(options.numberNonEmpty) { + options.numberAll = false; + } + + if(filePatterns.length === 0) { + console.log("cat: missing file operand"); + process.exit(1); + } + + const files = filePatterns; + + files.forEach((file) => { + const resolvedPath = path.resolve(process.cwd(), file); + printFile(resolvedPath, options); + }); + } + +main(); diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 000000000..7ba9c99cb --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,52 @@ +#!/usr/bin/env node +const fs = require('node:fs'); +const path = require('node:path'); + +function listDirectory(dirPath, showAll, onePerLine) { + try { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + const filtered = entries.filter((entry) => showAll || !entry.name.startsWith('.')); + + if(onePerLine) { + filtered.forEach(entry => console.log(entry.name)); + } else { + const names = filtered.map(entry => entry.name); + console.log(names.join(' ')); + } + } catch (error) { + console.error(`Error reading directory ${dirPath}: ${error.message}`); + } +} +function main() { + const args = process.argv.slice(2); + // Check for options + const showAll = args.includes('-a'); + const onePerLine = args.includes('-1'); + //remove options from args + const directories = args.filter(arg => arg !== '-a' && arg !== '-1'); + + // If no directory is specified, list the current directory + if(directories.length === 0) { + listDirectory(process.cwd(), showAll, onePerLine); + return; + } + //If a directory is specified, list that directory + directories.forEach((arg, index) => { + try { + const stats = fs.statSync(arg); + if(stats.isDirectory()) { + //Print header if multiple directories are listed + if(directories.length > 1) console.log(`${arg}:`); + + listDirectory(arg, showAll, onePerLine); + //add a blank line between directory listings if there are multiple directories + if(directories.length > 1 && index < directories.length - 1) console.log(''); + } else{ + console.log(arg);// single file + } + } catch (error) { + console.error(`Error accessing ${arg}: ${error.message}`); + } + }); +} +main(); \ No newline at end of file diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100755 index 000000000..58de824fc --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node +const fs = require('node:fs'); +// Function to count lines, words, and bytes in a file +function countFileContent(content) { + const lines = content.split('\n').length; // Count lines by splitting on newline characters + const words = content.trim().split(/\s+/).filter(Boolean).length; // Split by whitespace and filter out empty strings + const bytes = Buffer.byteLength(content, 'utf8'); + return { lines, words, bytes }; +} + +//print counts in the format of wc according to options +function printCounts(filePath, counts, options) { + const parts = []; + if(options.line) parts.push(counts.lines); + if(options.word) parts.push(counts.words); + if(options.byte) parts.push(counts.bytes); +//if no specific count options are provided, print all counts + if(!options.line && !options.word && !options.byte) { + //default is to print all counts + parts.push(counts.lines, counts.words, counts.bytes); + } + console.log(parts.join('\t'),filePath); +} + +function main() { + const args = process.argv.slice(2); + const options = { + line: false, + word: false, + byte: false, + }; + + //Separate options from file paths + const files = []; + args.forEach((arg) => { + if(arg === '-l') options.line = true; + else if(arg === '-w') options.word = true; + else if(arg === '-c') options.byte = true; + else files.push(arg); + }); + + if(files.length === 0) { + console.error('No files specified'); + process.exit(1); + } + + let totalCounts = { lines: 0, words: 0, bytes: 0 }; + const multipleFiles = files.length > 1; + + files.forEach(file => { + try { + const content = fs.readFileSync(file, 'utf-8'); + const counts = countFileContent(content); + + // Sum counts for total if multiple files + totalCounts.lines += counts.lines; + totalCounts.words += counts.words; + totalCounts.bytes += counts.bytes; + + printCounts(file, counts, options); + } catch (error) { + console.error(`Error reading file ${file}: ${error.message}`); + } + }); + + // If multiple files, print total counts + if(multipleFiles) { + printCounts('total', totalCounts, options); + } +} +main(); \ No newline at end of file From 972ae2b337192a9009f505f5895ee10a48191181 Mon Sep 17 00:00:00 2001 From: Ebrahim Beiaty Date: Mon, 16 Mar 2026 12:39:23 +0000 Subject: [PATCH 2/4] applied the feedback --- implement-shell-tools/cat/cat.js | 106 +++++++++++++---------------- implement-shell-tools/ls/ls.js | 86 ++++++++++++------------ implement-shell-tools/wc/wc.js | 110 ++++++++++++++++--------------- 3 files changed, 144 insertions(+), 158 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 2bdbfc039..0c70bc945 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,76 +1,60 @@ #!/usr/bin/env node -const fs = require('fs'); -const path = require('path'); - +const fs = require("node:fs"); //shared line counter across all files(matches cat -n) let globalLineCounter = 1; function printFile(filePath, options) { - try { - const content = fs.readFileSync(filePath, 'utf-8'); - const lines = content.split('\n'); + try { + const content = fs.readFileSync(filePath, "utf-8"); + const lines = content.split("\n"); + + lines.forEach((line) => { + let prefix = ""; + + if (options.numberMode === "non-empty") { + //-b option: number non-empty lines + if (line.trim() !== "") { + prefix = `${String(globalLineCounter).padStart(6)}\t`; + globalLineCounter++; + } + } else if (options.numberMode === "all") { + prefix = `${String(globalLineCounter).padStart(6)}\t`; + globalLineCounter++; + } - lines.forEach((line) => { - if(options.numberNonEmpty) { - //-b option: number non-empty lines - if(line.trim()) { - process.stdout.write( - `${String(globalLineCounter).padStart(6)}\t${line}\n` - ); - globalLineCounter++; - } else { - process.stdout.write('\n'); - } - } else if(options.numberAll) { - //-n option: number all lines - process.stdout.write( - `${String(globalLineCounter).padStart(6)}\t${line}\n` - ); - globalLineCounter++; - } else { - //default: just print the line - process.stdout.write(line + '\n'); - } + process.stdout.write(`${prefix}${line}\n`); }); - - } catch (error) { - console.error(`Error reading file ${filePath}: ${error.message}`); - } + } catch (error) { + console.error(`cat: ${filePath}: ${error.message}`); + } } function main() { - const args = process.argv.slice(2); - const options = { - numberNonEmpty: false, - numberAll: false, - }; - const filePatterns = []; - - args.forEach((arg) => { - if(arg === '-n') { - options.numberAll = true; - } else if(arg === '-b') { - options.numberNonEmpty = true; - } else { - filePatterns.push(arg); - } - }); - // -b takes precedence over -n - if(options.numberNonEmpty) { - options.numberAll = false; - } - - if(filePatterns.length === 0) { - console.log("cat: missing file operand"); - process.exit(1); + const args = process.argv.slice(2); + + const options = { + numberMode: "off", + }; + + const files = []; + args.forEach((arg) => { + if (arg === "-n") { + options.numberMode = "all"; + } else if (arg === "-b") { + options.numberMode = "non-empty"; + } else { + files.push(arg); } + }); - const files = filePatterns; + if (files.length === 0) { + console.log("cat: missing file operand"); + process.exit(1); + } - files.forEach((file) => { - const resolvedPath = path.resolve(process.cwd(), file); - printFile(resolvedPath, options); - }); - } + files.forEach((file) => { + printFile(file, options); + }); +} main(); diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 7ba9c99cb..0cd8152b8 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,52 +1,52 @@ #!/usr/bin/env node -const fs = require('node:fs'); -const path = require('node:path'); - +const fs = require("node:fs"); function listDirectory(dirPath, showAll, onePerLine) { - try { - const entries = fs.readdirSync(dirPath, { withFileTypes: true }); - const filtered = entries.filter((entry) => showAll || !entry.name.startsWith('.')); + try { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + const filtered = entries.filter( + (entry) => showAll || !entry.name.startsWith("."), + ); - if(onePerLine) { - filtered.forEach(entry => console.log(entry.name)); - } else { - const names = filtered.map(entry => entry.name); - console.log(names.join(' ')); - } - } catch (error) { - console.error(`Error reading directory ${dirPath}: ${error.message}`); + if (onePerLine) { + filtered.forEach((entry) => console.log(entry.name)); + } else { + const names = filtered.map((entry) => entry.name); + console.log(names.join(" ")); } + } catch (error) { + console.error(`Error reading directory ${dirPath}: ${error.message}`); + } } function main() { - const args = process.argv.slice(2); - // Check for options - const showAll = args.includes('-a'); - const onePerLine = args.includes('-1'); - //remove options from args - const directories = args.filter(arg => arg !== '-a' && arg !== '-1'); + const args = process.argv.slice(2); + // Check for options + const showAll = args.includes("-a"); + const onePerLine = args.includes("-1"); + //remove options from args + let directories = args.filter((arg) => arg !== "-a" && arg !== "-1"); + + // Default to current directory if no directories are specified + if (directories.length === 0) { + directories = [process.cwd()]; + } + //If a directory is specified, list that directory + directories.forEach((arg, index) => { + try { + const stats = fs.statSync(arg); - // If no directory is specified, list the current directory - if(directories.length === 0) { - listDirectory(process.cwd(), showAll, onePerLine); - return; + if (stats.isDirectory()) { + //Print header if multiple directories are listed + if (directories.length > 1) console.log(`${arg}:`); + listDirectory(arg, showAll, onePerLine); + //add a blank line between directory listings if there are multiple directories + if (directories.length > 1 && index < directories.length - 1) + console.log(""); + } else { + console.log(arg); // single file + } + } catch (error) { + console.error(`Error accessing ${arg}: ${error.message}`); } - //If a directory is specified, list that directory - directories.forEach((arg, index) => { - try { - const stats = fs.statSync(arg); - if(stats.isDirectory()) { - //Print header if multiple directories are listed - if(directories.length > 1) console.log(`${arg}:`); - - listDirectory(arg, showAll, onePerLine); - //add a blank line between directory listings if there are multiple directories - if(directories.length > 1 && index < directories.length - 1) console.log(''); - } else{ - console.log(arg);// single file - } - } catch (error) { - console.error(`Error accessing ${arg}: ${error.message}`); - } - }); + }); } -main(); \ No newline at end of file +main(); diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 58de824fc..8462286e5 100755 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -1,71 +1,73 @@ #!/usr/bin/env node -const fs = require('node:fs'); +const fs = require("node:fs"); // Function to count lines, words, and bytes in a file function countFileContent(content) { - const lines = content.split('\n').length; // Count lines by splitting on newline characters - const words = content.trim().split(/\s+/).filter(Boolean).length; // Split by whitespace and filter out empty strings - const bytes = Buffer.byteLength(content, 'utf8'); - return { lines, words, bytes }; + const lines = content.split("\n").length; // Count lines by splitting on newline characters + const words = content.trim().split(/\s+/).filter(Boolean).length; // Split by whitespace and filter out empty strings + const bytes = Buffer.byteLength(content, "utf8"); + return { lines, words, bytes }; } //print counts in the format of wc according to options function printCounts(filePath, counts, options) { - const parts = []; - if(options.line) parts.push(counts.lines); - if(options.word) parts.push(counts.words); - if(options.byte) parts.push(counts.bytes); -//if no specific count options are provided, print all counts - if(!options.line && !options.word && !options.byte) { - //default is to print all counts - parts.push(counts.lines, counts.words, counts.bytes); - } - console.log(parts.join('\t'),filePath); + const parts = []; + if (options.line) parts.push(counts.lines); + if (options.word) parts.push(counts.words); + if (options.byte) parts.push(counts.bytes); + + console.log(parts.join("\t"), filePath); } function main() { - const args = process.argv.slice(2); - const options = { - line: false, - word: false, - byte: false, - }; + const args = process.argv.slice(2); + const options = { + line: false, + word: false, + byte: false, + }; - //Separate options from file paths - const files = []; - args.forEach((arg) => { - if(arg === '-l') options.line = true; - else if(arg === '-w') options.word = true; - else if(arg === '-c') options.byte = true; - else files.push(arg); - }); + //Separate options from file paths + const files = []; + args.forEach((arg) => { + if (arg === "-l") options.line = true; + else if (arg === "-w") options.word = true; + else if (arg === "-c") options.byte = true; + else files.push(arg); + }); - if(files.length === 0) { - console.error('No files specified'); - process.exit(1); - } + if (files.length === 0) { + console.error("No files specified"); + process.exit(1); + } + //If no specific count options are provided, default to all counts (lines, words, bytes) + if (!options.line && !options.word && !options.byte) { + options.line = true; + options.word = true; + options.byte = true; + } - let totalCounts = { lines: 0, words: 0, bytes: 0 }; - const multipleFiles = files.length > 1; - - files.forEach(file => { - try { - const content = fs.readFileSync(file, 'utf-8'); - const counts = countFileContent(content); + let totalCounts = { lines: 0, words: 0, bytes: 0 }; + const multipleFiles = files.length > 1; - // Sum counts for total if multiple files - totalCounts.lines += counts.lines; - totalCounts.words += counts.words; - totalCounts.bytes += counts.bytes; + files.forEach((file) => { + try { + const content = fs.readFileSync(file, "utf-8"); + const counts = countFileContent(content); - printCounts(file, counts, options); - } catch (error) { - console.error(`Error reading file ${file}: ${error.message}`); - } - }); - - // If multiple files, print total counts - if(multipleFiles) { - printCounts('total', totalCounts, options); + // Sum counts for total if multiple files + totalCounts.lines += counts.lines; + totalCounts.words += counts.words; + totalCounts.bytes += counts.bytes; + + printCounts(file, counts, options); + } catch (error) { + console.error(`Error reading file ${file}: ${error.message}`); } + }); + + // If multiple files, print total counts + if (multipleFiles) { + printCounts("total", totalCounts, options); + } } -main(); \ No newline at end of file +main(); From 09cde630adba902a249831529bdbcb96168b680b Mon Sep 17 00:00:00 2001 From: Ebrahim Beiaty Date: Mon, 16 Mar 2026 13:47:04 +0000 Subject: [PATCH 3/4] applied the new feedbacks --- implement-shell-tools/wc/wc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 8462286e5..1f7623f02 100755 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -62,6 +62,7 @@ function main() { printCounts(file, counts, options); } catch (error) { console.error(`Error reading file ${file}: ${error.message}`); + process.exitCode = 1; } }); From 643fefa50470b98355b672c90e6074ec797c6a9d Mon Sep 17 00:00:00 2001 From: Ebrahim Beiaty Date: Mon, 16 Mar 2026 13:47:29 +0000 Subject: [PATCH 4/4] applied the new feedbacks --- implement-shell-tools/cat/cat.js | 12 +++++------- implement-shell-tools/ls/ls.js | 13 +++++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 0c70bc945..052ba794b 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -11,16 +11,14 @@ function printFile(filePath, options) { lines.forEach((line) => { let prefix = ""; - if (options.numberMode === "non-empty") { - //-b option: number non-empty lines - if (line.trim() !== "") { + const shouldNumber = + options.numberMode === "all" || + (options.numberMode === "non-empty" && line.trim() !== ""); + + if (shouldNumber) { prefix = `${String(globalLineCounter).padStart(6)}\t`; globalLineCounter++; } - } else if (options.numberMode === "all") { - prefix = `${String(globalLineCounter).padStart(6)}\t`; - globalLineCounter++; - } process.stdout.write(`${prefix}${line}\n`); }); diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 0cd8152b8..c4b5e88b7 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -15,6 +15,7 @@ function listDirectory(dirPath, showAll, onePerLine) { } } catch (error) { console.error(`Error reading directory ${dirPath}: ${error.message}`); + process.exitCode = 1; } } function main() { @@ -29,23 +30,27 @@ function main() { if (directories.length === 0) { directories = [process.cwd()]; } - //If a directory is specified, list that directory directories.forEach((arg, index) => { try { const stats = fs.statSync(arg); if (stats.isDirectory()) { //Print header if multiple directories are listed - if (directories.length > 1) console.log(`${arg}:`); + if (directories.length > 1) { + console.log(`${arg}:`) + }; listDirectory(arg, showAll, onePerLine); //add a blank line between directory listings if there are multiple directories - if (directories.length > 1 && index < directories.length - 1) - console.log(""); + if (directories.length > 1 && index < directories.length - 1){ + console.log(""); + } + } else { console.log(arg); // single file } } catch (error) { console.error(`Error accessing ${arg}: ${error.message}`); + process.exitCode = 1; } }); }