From 276fc2be1d76d7ee93ca1a78f2f3d80722ee4542 Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Sat, 21 Mar 2026 08:46:45 +0000 Subject: [PATCH 1/5] implementing a logic that behive as cat command --- implement-shell-tools/cat/cat.js | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 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 000000000..7c88bd963 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,40 @@ +import process from "node:process"; +import { promises as fs } from "node:fs"; + +// THis will give an array without the path to node and to the file. +const argv = process.argv.slice(2); + +//Get line numbers. +const showNumbers = argv.includes("-n"); +const showNonBlankNumbers = argv.includes("-b"); + +//filter the - from the array argv as it's a flag. +const filePaths = argv.filter((arg) => !arg.startsWith("-")); +let counterLines = 1; + +for (const path of filePaths) { + try { + const content = await fs.readFile(path, "utf-8"); + + //split the text at the new line character. + const splitLines = content.split("\n"); + + splitLines.forEach((line) => { + if (showNumbers) { + console.log(`${counterLines++} ${line}`); + } else if (showNonBlankNumbers) { + // increment and show numbers only if the line is not empty. + if (line.trim() !== "") { + console.log(`${counterLines++} ${line}`); + } else { + // print empty lines + console.log(line); + } + } else { + console.log(line); + } + }); + } catch (error) { + console.log(`Could not read: ${path}`); + } +} From 8675a8b2fca34151e3d2c70721e021bdb15e9417 Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Tue, 24 Mar 2026 14:24:42 +0000 Subject: [PATCH 2/5] Solving The exercise for ls command. --- implement-shell-tools/ls/ls.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 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 000000000..18b2116e0 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,28 @@ +import fs from "node:fs"; +import process from "node:process"; + +// This will give an array without the path to node and to the file. +const argv = process.argv.slice(2); + +// filter the flag to find the target folder. +const filePaths = argv.filter((arg) => !arg.startsWith("-")); +const showHiddenFiles = argv.includes("-a"); + +// if no folder provide we use the current one +const target = filePaths[0] || "."; +// read the file. +const files = fs.readdirSync(target); + +// save the result into the variable. +let filteredFIles = files; +if (!showHiddenFiles) { + filteredFIles = files.filter((file) => !file.startsWith(".")); +} else { + // we use spread operator to merge the paths. + filteredFIles = [".", "..", ...files]; +} + +// Print using -1 . +filteredFIles.forEach((file) => { + console.log(file); +}); From 99cb233b094f5a4c53bbdeaec48c7964a09e2570 Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Thu, 26 Mar 2026 11:00:56 +0000 Subject: [PATCH 3/5] feat: create a script that behave like wc command. --- implement-shell-tools/wc/wc.js | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 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 000000000..2860444ae --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,40 @@ +import process, { exit } from "node:process"; +import fs from "node:fs"; + +const argv = process.argv.slice(2); + +const showWords = argv.includes("-w"); +const showLines = argv.includes("-l"); +const showBytes = argv.includes("-c"); +const showCharacters = argv.includes("-m"); + +// filter flags, and getting the string of filename +const filePaths = argv.filter((arg) => !arg.startsWith("-")); + +const noFlags = !showLines && !showCharacters && !showWords && !showCharacters; +if (!filePaths) { + console.error("PLease provide a file path"); + process.exit(1); +} +// loop trough the array to get each file path. +filePaths.forEach((filePath) => { + const content = fs.readFileSync(filePath, "utf-8"); + + const lines = content.split("\n").length - 1; + const words = content + .trim() + .split(/\s+/) + .filter((word) => word != "").length; + // here I used Buffer.byteLength even if characters and bytes can be the same number .length, however sometimes an emoji or special characters can be heavier 2b or 4b + const bytes = Buffer.byteLength(content); + const characters = content.length; + + let output = ""; + + if (showLines || noFlags) output += `${lines} `; + if (showWords || noFlags) output += `${words} `; + if (showBytes || noFlags) output += `${bytes} `; + if (showCharacters || noFlags) output += `${characters} `; + + console.log(`${output} ${filePath}`); +}); From e1d7c80532a5eec8dc7b17dfb87eb254678b0fd0 Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Sat, 11 Apr 2026 09:30:48 +0100 Subject: [PATCH 4/5] FIx: Implement the missing logics. --- implement-shell-tools/.gitignore | 1 + implement-shell-tools/cat/cat.js | 30 +++++++++++++++++++----------- implement-shell-tools/ls/ls.js | 4 ++++ implement-shell-tools/wc/wc.js | 28 +++++++++++++++++----------- 4 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 implement-shell-tools/.gitignore diff --git a/implement-shell-tools/.gitignore b/implement-shell-tools/.gitignore new file mode 100644 index 000000000..1d17dae13 --- /dev/null +++ b/implement-shell-tools/.gitignore @@ -0,0 +1 @@ +.venv diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 7c88bd963..4c9b552a7 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -10,6 +10,16 @@ const showNonBlankNumbers = argv.includes("-b"); //filter the - from the array argv as it's a flag. const filePaths = argv.filter((arg) => !arg.startsWith("-")); + +const flagsUsed = argv.filter((arg) => arg.startsWith("-")); +const supportedFlags = ["-n", "-b"]; +for (const flag of flagsUsed) { + if (!supportedFlags.includes(flag)) { + console.error(`Invalid option try 'node cat.js --help' for more info.`); + process.exit(1); + } +} + let counterLines = 1; for (const path of filePaths) { @@ -20,21 +30,19 @@ for (const path of filePaths) { const splitLines = content.split("\n"); splitLines.forEach((line) => { - if (showNumbers) { - console.log(`${counterLines++} ${line}`); - } else if (showNonBlankNumbers) { - // increment and show numbers only if the line is not empty. + let prefix = ""; + + if (showNonBlankNumbers) { if (line.trim() !== "") { - console.log(`${counterLines++} ${line}`); - } else { - // print empty lines - console.log(line); + prefix = `${counterLines++} `; } - } else { - console.log(line); + } else if (showNumbers) { + prefix = `${counterLines++} `; } + console.log(`${prefix}${line}`); }); } catch (error) { - console.log(`Could not read: ${path}`); + console.error(`Could not read: ${path}`); + process.exit(1); } } diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 18b2116e0..e1d3b4b75 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -8,6 +8,10 @@ const argv = process.argv.slice(2); const filePaths = argv.filter((arg) => !arg.startsWith("-")); const showHiddenFiles = argv.includes("-a"); +if (filePaths.length > 1) { + console.error("Error: This version only supports one directory path a time."); + process.exit(1); +} // if no folder provide we use the current one const target = filePaths[0] || "."; // read the file. diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 2860444ae..44e1a1429 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -3,16 +3,22 @@ import fs from "node:fs"; const argv = process.argv.slice(2); -const showWords = argv.includes("-w"); -const showLines = argv.includes("-l"); -const showBytes = argv.includes("-c"); -const showCharacters = argv.includes("-m"); +let showWords = argv.includes("-w"); +let showLines = argv.includes("-l"); +let showBytes = argv.includes("-c"); +let showCharacters = argv.includes("-m"); // filter flags, and getting the string of filename const filePaths = argv.filter((arg) => !arg.startsWith("-")); - -const noFlags = !showLines && !showCharacters && !showWords && !showCharacters; -if (!filePaths) { +// if no flags enable all. +if (!showLines && !showCharacters && !showWords && !showBytes) { + showLines = true; + showCharacters = true; + showWords = true; + showBytes = true; +} +// fix bug .length ===0 will be true if nothing provided, instead !filePath will return empty array which result true. +if (filePaths.length === 0) { console.error("PLease provide a file path"); process.exit(1); } @@ -31,10 +37,10 @@ filePaths.forEach((filePath) => { let output = ""; - if (showLines || noFlags) output += `${lines} `; - if (showWords || noFlags) output += `${words} `; - if (showBytes || noFlags) output += `${bytes} `; - if (showCharacters || noFlags) output += `${characters} `; + if (showLines) output += `${lines} `; + if (showWords) output += `${words} `; + if (showBytes) output += `${bytes} `; + if (showCharacters) output += `${characters} `; console.log(`${output} ${filePath}`); }); From 2bec857b75d5cc0bd45721d55b04d6969527ee1b Mon Sep 17 00:00:00 2001 From: Jamal Laqdiem Date: Sat, 25 Apr 2026 09:19:43 +0100 Subject: [PATCH 5/5] fixed the errors. --- implement-shell-tools/cat/cat.js | 12 ++---- implement-shell-tools/ls/ls.js | 9 +---- implement-shell-tools/wc/wc.js | 67 +++++++++++++++++++++----------- 3 files changed, 50 insertions(+), 38 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 4c9b552a7..c0a26279f 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,21 +1,18 @@ import process from "node:process"; import { promises as fs } from "node:fs"; -// THis will give an array without the path to node and to the file. const argv = process.argv.slice(2); -//Get line numbers. const showNumbers = argv.includes("-n"); const showNonBlankNumbers = argv.includes("-b"); -//filter the - from the array argv as it's a flag. const filePaths = argv.filter((arg) => !arg.startsWith("-")); const flagsUsed = argv.filter((arg) => arg.startsWith("-")); const supportedFlags = ["-n", "-b"]; for (const flag of flagsUsed) { if (!supportedFlags.includes(flag)) { - console.error(`Invalid option try 'node cat.js --help' for more info.`); + console.error(`Invalid option: please try "-n or "-b"`); process.exit(1); } } @@ -25,9 +22,8 @@ let counterLines = 1; for (const path of filePaths) { try { const content = await fs.readFile(path, "utf-8"); - - //split the text at the new line character. - const splitLines = content.split("\n"); +. + const splitLines = content.split("\n"); splitLines.forEach((line) => { let prefix = ""; @@ -42,7 +38,7 @@ for (const path of filePaths) { console.log(`${prefix}${line}`); }); } catch (error) { - console.error(`Could not read: ${path}`); + console.error(`Error ${path}: ${error.message}`); process.exit(1); } } diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index e1d3b4b75..1e30c42a8 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,10 +1,8 @@ import fs from "node:fs"; import process from "node:process"; -// This will give an array without the path to node and to the file. const argv = process.argv.slice(2); -// filter the flag to find the target folder. const filePaths = argv.filter((arg) => !arg.startsWith("-")); const showHiddenFiles = argv.includes("-a"); @@ -12,21 +10,18 @@ if (filePaths.length > 1) { console.error("Error: This version only supports one directory path a time."); process.exit(1); } -// if no folder provide we use the current one + const target = filePaths[0] || "."; -// read the file. + const files = fs.readdirSync(target); -// save the result into the variable. let filteredFIles = files; if (!showHiddenFiles) { filteredFIles = files.filter((file) => !file.startsWith(".")); } else { - // we use spread operator to merge the paths. filteredFIles = [".", "..", ...files]; } -// Print using -1 . filteredFIles.forEach((file) => { console.log(file); }); diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 44e1a1429..f10faf9e4 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -8,7 +8,6 @@ let showLines = argv.includes("-l"); let showBytes = argv.includes("-c"); let showCharacters = argv.includes("-m"); -// filter flags, and getting the string of filename const filePaths = argv.filter((arg) => !arg.startsWith("-")); // if no flags enable all. if (!showLines && !showCharacters && !showWords && !showBytes) { @@ -17,30 +16,52 @@ if (!showLines && !showCharacters && !showWords && !showBytes) { showWords = true; showBytes = true; } -// fix bug .length ===0 will be true if nothing provided, instead !filePath will return empty array which result true. + if (filePaths.length === 0) { console.error("PLease provide a file path"); process.exit(1); } -// loop trough the array to get each file path. + +let totalLines = 0; +let totalWords = 0; +let totalBytes = 0; +let totalChars = 0; + filePaths.forEach((filePath) => { - const content = fs.readFileSync(filePath, "utf-8"); - - const lines = content.split("\n").length - 1; - const words = content - .trim() - .split(/\s+/) - .filter((word) => word != "").length; - // here I used Buffer.byteLength even if characters and bytes can be the same number .length, however sometimes an emoji or special characters can be heavier 2b or 4b - const bytes = Buffer.byteLength(content); - const characters = content.length; - - let output = ""; - - if (showLines) output += `${lines} `; - if (showWords) output += `${words} `; - if (showBytes) output += `${bytes} `; - if (showCharacters) output += `${characters} `; - - console.log(`${output} ${filePath}`); -}); + try { + const content = fs.readFileSync(filePath, "utf-8"); + const lineArray = content.split("\n"); + if (content.endsWith("\n")) lineArray.pop(); + const lines = lineArray.length; + + const words = content.trim().split(/\s+/).filter((word) => word != "").length; + const bytes = Buffer.byteLength(content); + const characters = content.length; + + totalLines += lines; + totalWords += words; + totalBytes += bytes; + totalChars += characters; + + let output = ""; + if (showLines) output += lines.toString().padStart(8); + if (showWords) output += words.toString().padStart(8); + if (showBytes) output += bytes.toString().padStart(8); + if (showCharacters) output += characters.toString().padStart(8) + + console.log(`${output} ${filePath}`); + } catch(error) { + console.error(`Error reading ${filePath}: ${error.message}`); + } +}); + +if (filePaths.length > 1) { + let totalOutput = ""; + if (showLines) totalOutput += `${totalLines.toString().padStart(8)}`; + if (showWords) totalOutput += `${totalWords.toString().padStart(8)}`; + if (showBytes) totalOutput += `${totalBytes.toString().padStart(8)}`; + if (showCharacters) totalOutput += `${totalChars.toString().padStart(8)}`; + + console.log(`${totalOutput} total`); +} +