From 0082e59af44af11575cae7938a7857f6d56fa482 Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Fri, 18 Jul 2025 17:18:55 +0100 Subject: [PATCH 01/12] Completed ls-grep exercises --- shell-pipelines/ls-grep/script-01.sh | 2 ++ shell-pipelines/ls-grep/script-02.sh | 3 +++ shell-pipelines/ls-grep/script-03.sh | 2 ++ shell-pipelines/ls-grep/script-04.sh | 2 ++ 4 files changed, 9 insertions(+) diff --git a/shell-pipelines/ls-grep/script-01.sh b/shell-pipelines/ls-grep/script-01.sh index 8c7d968a2..87c191290 100755 --- a/shell-pipelines/ls-grep/script-01.sh +++ b/shell-pipelines/ls-grep/script-01.sh @@ -4,3 +4,5 @@ set -euo pipefail # TODO: Write a command to output the names of the files in the sample-files directory whose name contains at least one upper case letter. # Your output should contain 11 files. + +ls sample-files | grep '[A-Z]' diff --git a/shell-pipelines/ls-grep/script-02.sh b/shell-pipelines/ls-grep/script-02.sh index 16f5f71d9..bf4e06cdd 100755 --- a/shell-pipelines/ls-grep/script-02.sh +++ b/shell-pipelines/ls-grep/script-02.sh @@ -4,3 +4,6 @@ set -euo pipefail # TODO: Write a command to output the names of the files in the sample-files directory whose name starts with an upper case letter. # Your output should contain 10 files. + +ls sample-files | grep '^[A-Z]' + diff --git a/shell-pipelines/ls-grep/script-03.sh b/shell-pipelines/ls-grep/script-03.sh index a302ab036..efcd84613 100755 --- a/shell-pipelines/ls-grep/script-03.sh +++ b/shell-pipelines/ls-grep/script-03.sh @@ -4,3 +4,5 @@ set -euo pipefail # TODO: Write a command to output the names of the files in the sample-files directory whose name starts with an upper case letter and doesn't contain any other upper case letters. # Your output should contain 7 files. + +ls sample-files | grep -E '^[A-Z][^A-Z]*$' diff --git a/shell-pipelines/ls-grep/script-04.sh b/shell-pipelines/ls-grep/script-04.sh index c000b7e3b..2da3786f2 100755 --- a/shell-pipelines/ls-grep/script-04.sh +++ b/shell-pipelines/ls-grep/script-04.sh @@ -4,3 +4,5 @@ set -euo pipefail # TODO: Write a command to count the number of files in the sample-files directory whose name starts with an upper case letter and doesn't contain any other upper case letters. # Your output should be the number 7. + +ls sample-files | grep -E '^[A-Z][^A-Z]*$' | wc -l \ No newline at end of file From fedfb04ae1f186113db9a957b9a40f6f7ea8dbc2 Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Fri, 18 Jul 2025 17:20:30 +0100 Subject: [PATCH 02/12] Completed sort-uniq-head-tail exercises. --- shell-pipelines/sort-uniq-head-tail/script-01.sh | 2 ++ shell-pipelines/sort-uniq-head-tail/script-02.sh | 3 +++ shell-pipelines/sort-uniq-head-tail/script-03.sh | 2 ++ shell-pipelines/sort-uniq-head-tail/script-04.sh | 2 ++ shell-pipelines/sort-uniq-head-tail/script-05.sh | 2 ++ shell-pipelines/sort-uniq-head-tail/script-06.sh | 3 +++ shell-pipelines/sort-uniq-head-tail/script-07.sh | 4 ++++ 7 files changed, 18 insertions(+) diff --git a/shell-pipelines/sort-uniq-head-tail/script-01.sh b/shell-pipelines/sort-uniq-head-tail/script-01.sh index 171e1f989..038386659 100755 --- a/shell-pipelines/sort-uniq-head-tail/script-01.sh +++ b/shell-pipelines/sort-uniq-head-tail/script-01.sh @@ -5,3 +5,5 @@ set -euo pipefail # The input for this script is the scores-table.txt file. # TODO: Write a command to output scores-table.txt, with lines sorted by the person's name. # The first line of your output should be "Ahmed London 1 10 4" (with no quotes). And the third line should be "Chandra Birmingham 12 6". + +sort scores-table.txt diff --git a/shell-pipelines/sort-uniq-head-tail/script-02.sh b/shell-pipelines/sort-uniq-head-tail/script-02.sh index 29c3c2524..3853cc614 100755 --- a/shell-pipelines/sort-uniq-head-tail/script-02.sh +++ b/shell-pipelines/sort-uniq-head-tail/script-02.sh @@ -5,3 +5,6 @@ set -euo pipefail # The input for this script is the scores-table.txt file. # TODO: Write a command to output scores-table.txt, with lines sorted by the person's first score, descending. # The first line of your output should be "Basia London 22 9 6" (with no quotes). + + +sort -k3,3 -nr scores-table.txt diff --git a/shell-pipelines/sort-uniq-head-tail/script-03.sh b/shell-pipelines/sort-uniq-head-tail/script-03.sh index bcbaf3420..3fb062dc1 100755 --- a/shell-pipelines/sort-uniq-head-tail/script-03.sh +++ b/shell-pipelines/sort-uniq-head-tail/script-03.sh @@ -8,3 +8,5 @@ set -euo pipefail # Basia London 22 9 6 # Piotr Glasgow 15 2 25 11 8 # Chandra Birmingham 12 6 + +sort -k3,3 -nr scores-table.txt | head -n 3 \ No newline at end of file diff --git a/shell-pipelines/sort-uniq-head-tail/script-04.sh b/shell-pipelines/sort-uniq-head-tail/script-04.sh index 65a5cfba8..6809dfe7d 100755 --- a/shell-pipelines/sort-uniq-head-tail/script-04.sh +++ b/shell-pipelines/sort-uniq-head-tail/script-04.sh @@ -5,3 +5,5 @@ set -euo pipefail # The input for this script is the scores-table.txt file. # TODO: Write a command to output scores-table.txt, with shows the line for the player whose first score was the second highest. # Your output should be: "Piotr Glasgow 15 2 25 11 8" (without quotes). + +sort -k3,3 -nr scores-table.txt | head -n 2 | tail -n 1 diff --git a/shell-pipelines/sort-uniq-head-tail/script-05.sh b/shell-pipelines/sort-uniq-head-tail/script-05.sh index a93cd9f9d..7bba7d19a 100755 --- a/shell-pipelines/sort-uniq-head-tail/script-05.sh +++ b/shell-pipelines/sort-uniq-head-tail/script-05.sh @@ -6,3 +6,5 @@ set -euo pipefail # TODO: Write a command to show a list of all events that have happened, without duplication. # The order they're displayed doesn't matter, but we never want to see the same event listed twice. # Your output should contain 6 lines. + +sort events.txt | uniq diff --git a/shell-pipelines/sort-uniq-head-tail/script-06.sh b/shell-pipelines/sort-uniq-head-tail/script-06.sh index 715c7ae5c..4d8a50c5c 100755 --- a/shell-pipelines/sort-uniq-head-tail/script-06.sh +++ b/shell-pipelines/sort-uniq-head-tail/script-06.sh @@ -5,3 +5,6 @@ set -euo pipefail # The input for this script is the events.txt file. # TODO: Write a command to show how many times anyone has entered and exited. # It should be clear from your script's output that there have been 5 Entry events and 4 Exit events. + + +awk '{print $3}' events.txt | sort | uniq -c diff --git a/shell-pipelines/sort-uniq-head-tail/script-07.sh b/shell-pipelines/sort-uniq-head-tail/script-07.sh index 7fd07e1ff..23cc6c898 100755 --- a/shell-pipelines/sort-uniq-head-tail/script-07.sh +++ b/shell-pipelines/sort-uniq-head-tail/script-07.sh @@ -6,3 +6,7 @@ set -euo pipefail # TODO: Write a command to show how many times anyone has entered and exited. # It should be clear from your script's output that there have been 5 Entry events and 4 Exit events. # The word "Event" should not appear in your script's output. + + +awk '$3 == "Entry" || $3 == "Exit" {print $3}' events-with-timestamps.txt | sort | uniq -c + From 18f4d32c9d6365b1980ee39aa1ce84853506c59d Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Fri, 18 Jul 2025 17:20:55 +0100 Subject: [PATCH 03/12] Completed tr exercises. --- shell-pipelines/tr/script-01.sh | 2 ++ shell-pipelines/tr/script-02.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/shell-pipelines/tr/script-01.sh b/shell-pipelines/tr/script-01.sh index 8bb0211e9..b638ad8f6 100755 --- a/shell-pipelines/tr/script-01.sh +++ b/shell-pipelines/tr/script-01.sh @@ -6,3 +6,5 @@ set -euo pipefail # The author got feedback that they're using too many exclamation marks (!). # # TODO: Write a command to output the contents of text.txt with every exclamation mark (!) replaced with a full-stop (.). + +tr '!' '.' < text.txt diff --git a/shell-pipelines/tr/script-02.sh b/shell-pipelines/tr/script-02.sh index cf3a503a2..126177b90 100755 --- a/shell-pipelines/tr/script-02.sh +++ b/shell-pipelines/tr/script-02.sh @@ -7,3 +7,5 @@ set -euo pipefail # so every Y should be a Z, and every Z should be a Y! # # TODO: Write a command to output the contents of text.txt with every Y and Z swapped (both upper and lower case). + +tr 'yYzZ' 'zZyY' < text.txt From a0f8ccbf38f4df6334c7c9bd83af564216df3eb1 Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Fri, 25 Jul 2025 15:41:20 +0100 Subject: [PATCH 04/12] Added a .gitignore file. --- implement-shell-tools/.gitignore | 7 +++++++ 1 file changed, 7 insertions(+) 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..e6e0086b4 --- /dev/null +++ b/implement-shell-tools/.gitignore @@ -0,0 +1,7 @@ +# Ignore node_modules everywhere +**/node_modules/ + +#ignore logs, environment files. +*.log +.env +dist/ From af21d2c0d7ed712cc16bd07ff501266701d1c840 Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Fri, 25 Jul 2025 15:43:17 +0100 Subject: [PATCH 05/12] cat command implementation. --- implement-shell-tools/cat/cat.js | 58 +++++++++++++++++++++ implement-shell-tools/cat/package-lock.json | 25 +++++++++ implement-shell-tools/cat/package.json | 15 ++++++ 3 files changed, 98 insertions(+) create mode 100644 implement-shell-tools/cat/cat.js create mode 100644 implement-shell-tools/cat/package-lock.json create mode 100644 implement-shell-tools/cat/package.json diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 000000000..831a683e3 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,58 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import process from "node:process"; + +program + .name("my-cat") + .description("Reads and prints one or more files, optionally numbering lines continuously") + .argument("", "One or more file paths") + .option("-n, --number", "Number all output lines", false) + .option("-b, --number-nonblank", "Number non-empty output lines", false); + +program.parse(); + +const filePaths = program.args; +const options = program.opts(); + +const numberNonBlank = options.numberNonblank; +const numberAll = options.number && !numberNonBlank; + +(async () => { + let lineNumber = 1; // continuous line numbering + + for (const path of filePaths) { + try { + const content = await fs.readFile(path, "utf-8"); + const lines = content.split("\n"); + + if (numberNonBlank) { + const maxDigits = String(lines.filter(line => line.trim() !== '').length).length; + + for (const line of lines) { + if (line.trim() === '') { + process.stdout.write("\n"); + } else { + const numStr = String(lineNumber++).padStart(maxDigits, " "); + process.stdout.write(`${numStr}\t${line}\n`); + } + } + } else if (numberAll) { + const maxDigits = String(lines.length * filePaths.length).length; // max digits to pad across files + + for (const line of lines) { + const numStr = String(lineNumber++).padStart(maxDigits, " "); + process.stdout.write(`${numStr}\t${line}\n`); + } + } else { + // no numbering, just print file content as is + process.stdout.write(content); + if (!content.endsWith("\n")) { + process.stdout.write("\n"); // ensure newline after file if missing + } + } + } catch (err) { + console.error(`Error reading file "${path}": ${err.message}`); + process.exit(1); + } + } +})(); diff --git a/implement-shell-tools/cat/package-lock.json b/implement-shell-tools/cat/package-lock.json new file mode 100644 index 000000000..7c3bcb9aa --- /dev/null +++ b/implement-shell-tools/cat/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "cat", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cat", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/cat/package.json b/implement-shell-tools/cat/package.json new file mode 100644 index 000000000..58ba25a23 --- /dev/null +++ b/implement-shell-tools/cat/package.json @@ -0,0 +1,15 @@ +{ + "name": "cat", + "version": "1.0.0", + "description": "You should already be familiar with the `cat` command line tool.", + "main": "cat.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "commander": "^14.0.0" + } +} From 4179068cc3d6e07655d2dcb247e96fdc9581bd5b Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Fri, 25 Jul 2025 15:43:37 +0100 Subject: [PATCH 06/12] ls command implementation. --- implement-shell-tools/ls/ls.js | 69 ++++++++++++++++++++++ implement-shell-tools/ls/package-lock.json | 21 +++++++ implement-shell-tools/ls/package.json | 17 ++++++ 3 files changed, 107 insertions(+) create mode 100644 implement-shell-tools/ls/ls.js create mode 100644 implement-shell-tools/ls/package-lock.json create mode 100644 implement-shell-tools/ls/package.json diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 000000000..8925dd7d6 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,69 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import path from "node:path"; + +program + .name("my-ls") + .description("List files in a directory (simplified ls implementation)") + .argument("[paths...]", "One or more file or directory paths") + .option("-l, --longList", "Long listing format", false) + .option("-a, --all", "Include hidden files", false); + +program.parse(); + +// CHANGED: apply default value for paths here if none are provided +let filePaths = program.args.length > 0 ? program.args : ['.']; +const options = program.opts(); + +const showLong = options.longList; +const showAll = options.all; + +// Helper function to convert mode to rwxrwxrwx string +function formatPermissions(mode, isDir) { + const typeChar = isDir ? 'd' : '-'; + const perms = [0, 1, 2].map(i => { + const shift = 6 - i * 3; + const r = (mode >> shift) & 4 ? 'r' : '-'; + const w = (mode >> shift) & 2 ? 'w' : '-'; + const x = (mode >> shift) & 1 ? 'x' : '-'; + return r + w + x; + }).join(''); + return typeChar + perms; +} + +(async () => { + for (const inputPath of filePaths) { + try { + const stat = await fs.stat(inputPath); + + if (stat.isFile()) { + if (showLong) { + const perms = formatPermissions(stat.mode, false); + console.log(`${perms} ${stat.size.toString().padStart(6)} ${inputPath}`); + } else { + console.log(inputPath); + } + } else if (stat.isDirectory()) { + const entries = await fs.readdir(inputPath, { withFileTypes: true }); + + const filtered = showAll + ? entries + : entries.filter((entry) => !entry.name.startsWith(".")); + + for (const entry of filtered) { + const fullPath = path.join(inputPath, entry.name); + const entryStat = await fs.stat(fullPath); + if (showLong) { + const perms = formatPermissions(entryStat.mode, entryStat.isDirectory()); + console.log(`${perms} ${entryStat.size.toString().padStart(6)} ${entry.name}`); + } else { + console.log(entry.name); + } + } + } + } catch (err) { + console.error(`Error reading "${inputPath}": ${err.message}`); + process.exit(1); + } + } +})(); diff --git a/implement-shell-tools/ls/package-lock.json b/implement-shell-tools/ls/package-lock.json new file mode 100644 index 000000000..bff254485 --- /dev/null +++ b/implement-shell-tools/ls/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "ls", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "commander": "^14.0.0" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/ls/package.json b/implement-shell-tools/ls/package.json new file mode 100644 index 000000000..1ca3878d3 --- /dev/null +++ b/implement-shell-tools/ls/package.json @@ -0,0 +1,17 @@ +{ + "dependencies": { + "commander": "^14.0.0" + }, + "name": "ls", + "version": "1.0.0", + "description": "You should already be familiar with the `ls` command line tool.", + "main": "ls.js", + "type": "module", + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} From 523342ad392385d21c3b9918c3572a557b5ad604 Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Fri, 25 Jul 2025 15:43:54 +0100 Subject: [PATCH 07/12] wc command implementation. --- implement-shell-tools/wc/package-lock.json | 21 ++++++ implement-shell-tools/wc/package.json | 6 ++ implement-shell-tools/wc/wc.js | 86 ++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 implement-shell-tools/wc/package-lock.json create mode 100644 implement-shell-tools/wc/package.json create mode 100644 implement-shell-tools/wc/wc.js diff --git a/implement-shell-tools/wc/package-lock.json b/implement-shell-tools/wc/package-lock.json new file mode 100644 index 000000000..b3a3f4f8d --- /dev/null +++ b/implement-shell-tools/wc/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "wc", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "commander": "^14.0.0" + } + }, + "node_modules/commander": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "license": "MIT", + "engines": { + "node": ">=20" + } + } + } +} diff --git a/implement-shell-tools/wc/package.json b/implement-shell-tools/wc/package.json new file mode 100644 index 000000000..68279600c --- /dev/null +++ b/implement-shell-tools/wc/package.json @@ -0,0 +1,6 @@ +{ + "type": "module", + "dependencies": { + "commander": "^14.0.0" + } +} diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 000000000..b2ac3e02a --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,86 @@ +import { program } from "commander"; +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { stat } from "node:fs/promises"; + +program + .name("my-wc") + .description("simplified implementation of wc") + .argument("[paths...]", "One or more file or directory paths") + .option("-l, --line", "count lines") + .option("-w, --word", "count words") + .option("-c, --character", "count characters"); + +program.parse(); + +const filePaths = program.args.length > 0 ? program.args : ['.']; +const options = program.opts(); + +const countContent = (content) => { + const lines = content.split('\n').length; + const words = content.trim().split(/\s+/).filter(Boolean).length; + const characters = content.length; + return { lines, words, characters }; +}; + +const total = { + lines: 0, + words: 0, + characters: 0 +}; + +(async () => { + let fileCount = 0; + + for (const inputPath of filePaths) { + try { + const stats = await stat(inputPath); + if (stats.isDirectory()) { + console.log(`${inputPath} is a directory. Skipping.`); + continue; + } + + const content = await fs.readFile(inputPath, "utf-8"); + const { lines, words, characters } = countContent(content); + + total.lines += lines; + total.words += words; + total.characters += characters; + fileCount++; + + let output = ""; + if (options.line) output += `${lines.toString().padStart(8)} `; + if (options.word) output += `${words.toString().padStart(8)} `; + if (options.character) output += `${characters.toString().padStart(8)} `; + + // If no options given, show all + if (!options.line && !options.word && !options.character) { + output += `${lines.toString().padStart(8)} `; + output += `${words.toString().padStart(8)} `; + output += `${characters.toString().padStart(8)} `; + } + + output += inputPath; + console.log(output); + } catch (err) { + console.error(`Error reading "${inputPath}": ${err.message}`); + } + } + + // Print total only if multiple files were processed + if (fileCount > 1) { + let output = ""; + if (options.line) output += `${total.lines.toString().padStart(8)} `; + if (options.word) output += `${total.words.toString().padStart(8)} `; + if (options.character) output += `${total.characters.toString().padStart(8)} `; + + if (!options.line && !options.word && !options.character) { + output += `${total.lines.toString().padStart(8)} `; + output += `${total.words.toString().padStart(8)} `; + output += `${total.characters.toString().padStart(8)} `; + } + + output += "total"; + console.log(output); + } +})(); From 1b1928eb58e6b6bcbfecdb4d11c3429063ac4868 Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Sat, 9 Aug 2025 12:47:57 +0100 Subject: [PATCH 08/12] .venv added to gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3c3629e64..274d04915 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +.venv \ No newline at end of file From f2279d96957bb7f5ac2a887c038105d7a77be406 Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Tue, 14 Oct 2025 12:29:20 +0100 Subject: [PATCH 09/12] Fix according to PR reviews --- implement-shell-tools/cat/cat.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 831a683e3..639bd0c94 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -37,7 +37,8 @@ const numberAll = options.number && !numberNonBlank; } } } else if (numberAll) { - const maxDigits = String(lines.length * filePaths.length).length; // max digits to pad across files + const maxDigits = totalLines > 0 ? String(totalLines).length : 1; + for (const line of lines) { const numStr = String(lineNumber++).padStart(maxDigits, " "); From 7ca6ac127b212ff6308285f4c21c5f89ed124703 Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Tue, 14 Oct 2025 12:33:48 +0100 Subject: [PATCH 10/12] Added a module type declaration. --- implement-shell-tools/cat/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/implement-shell-tools/cat/package.json b/implement-shell-tools/cat/package.json index 58ba25a23..4ef36c57d 100644 --- a/implement-shell-tools/cat/package.json +++ b/implement-shell-tools/cat/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "You should already be familiar with the `cat` command line tool.", "main": "cat.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, From 1b40c9d80f914ff5f6791f0b1cd0be5dbd14b0ae Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Tue, 14 Oct 2025 12:40:36 +0100 Subject: [PATCH 11/12] Implemented an output formatting function. --- implement-shell-tools/wc/wc.js | 102 +++++++++++++++------------------ 1 file changed, 45 insertions(+), 57 deletions(-) diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index b2ac3e02a..4d5588abe 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -1,15 +1,14 @@ import { program } from "commander"; import { promises as fs } from "node:fs"; -import path from "node:path"; import { stat } from "node:fs/promises"; program .name("my-wc") - .description("simplified implementation of wc") + .description("Simplified implementation of wc") .argument("[paths...]", "One or more file or directory paths") - .option("-l, --line", "count lines") - .option("-w, --word", "count words") - .option("-c, --character", "count characters"); + .option("-l, --line", "Count lines") + .option("-w, --word", "Count words") + .option("-c, --character", "Count characters"); program.parse(); @@ -17,70 +16,59 @@ const filePaths = program.args.length > 0 ? program.args : ['.']; const options = program.opts(); const countContent = (content) => { - const lines = content.split('\n').length; - const words = content.trim().split(/\s+/).filter(Boolean).length; - const characters = content.length; - return { lines, words, characters }; + const lines = content.split('\n').length; + const words = content.trim().split(/\s+/).filter(Boolean).length; + const characters = content.length; + return { lines, words, characters }; }; const total = { - lines: 0, - words: 0, - characters: 0 + lines: 0, + words: 0, + characters: 0 }; -(async () => { - let fileCount = 0; +//Output formatting function +function formatOutput(counts, label, options) { + const { lines, words, characters } = counts; + const showAll = !options.line && !options.word && !options.character; - for (const inputPath of filePaths) { - try { - const stats = await stat(inputPath); - if (stats.isDirectory()) { - console.log(`${inputPath} is a directory. Skipping.`); - continue; - } + const parts = []; - const content = await fs.readFile(inputPath, "utf-8"); - const { lines, words, characters } = countContent(content); + if (options.line || showAll) parts.push(lines.toString().padStart(8)); + if (options.word || showAll) parts.push(words.toString().padStart(8)); + if (options.character || showAll) parts.push(characters.toString().padStart(8)); - total.lines += lines; - total.words += words; - total.characters += characters; - fileCount++; + parts.push(label); + return parts.join(" "); +} - let output = ""; - if (options.line) output += `${lines.toString().padStart(8)} `; - if (options.word) output += `${words.toString().padStart(8)} `; - if (options.character) output += `${characters.toString().padStart(8)} `; +(async () => { + let fileCount = 0; - // If no options given, show all - if (!options.line && !options.word && !options.character) { - output += `${lines.toString().padStart(8)} `; - output += `${words.toString().padStart(8)} `; - output += `${characters.toString().padStart(8)} `; - } + for (const inputPath of filePaths) { + try { + const stats = await stat(inputPath); + if (stats.isDirectory()) { + console.log(`${inputPath} is a directory. Skipping.`); + continue; + } - output += inputPath; - console.log(output); - } catch (err) { - console.error(`Error reading "${inputPath}": ${err.message}`); - } - } + const content = await fs.readFile(inputPath, "utf-8"); + const counts = countContent(content); - // Print total only if multiple files were processed - if (fileCount > 1) { - let output = ""; - if (options.line) output += `${total.lines.toString().padStart(8)} `; - if (options.word) output += `${total.words.toString().padStart(8)} `; - if (options.character) output += `${total.characters.toString().padStart(8)} `; + total.lines += counts.lines; + total.words += counts.words; + total.characters += counts.characters; + fileCount++; - if (!options.line && !options.word && !options.character) { - output += `${total.lines.toString().padStart(8)} `; - output += `${total.words.toString().padStart(8)} `; - output += `${total.characters.toString().padStart(8)} `; - } - - output += "total"; - console.log(output); + console.log(formatOutput(counts, inputPath, options)); + } catch (err) { + console.error(`Error reading "${inputPath}": ${err.message}`); } + } + + if (fileCount > 1) { + console.log(formatOutput(total, "total", options)); + } })(); From 784057b5403a5cd1756b15a9942441932a9cc1cc Mon Sep 17 00:00:00 2001 From: eyuell21 Date: Tue, 3 Mar 2026 15:25:27 +0000 Subject: [PATCH 12/12] Calculate total lines for -n option in cat command implementation --- implement-shell-tools/cat/cat.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 639bd0c94..624b885ac 100644 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -18,7 +18,23 @@ const numberNonBlank = options.numberNonblank; const numberAll = options.number && !numberNonBlank; (async () => { - let lineNumber = 1; // continuous line numbering + let lineNumber = 1; + + // calculate total lines if using -n + let totalLines = 0; + if (numberAll) { + for (const path of filePaths) { + try { + const content = await fs.readFile(path, "utf-8"); + const lines = content.split("\n"); + + totalLines += content.endsWith("\n") ? lines.length - 1 : lines.length; + } catch (err) { + console.error(`Error reading file "${path}": ${err.message}`); + process.exit(1); + } + } + } for (const path of filePaths) { try {