diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 000000000..052ba794b --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,58 @@ +#!/usr/bin/env node +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"); + + lines.forEach((line) => { + let prefix = ""; + + const shouldNumber = + options.numberMode === "all" || + (options.numberMode === "non-empty" && line.trim() !== ""); + + if (shouldNumber) { + prefix = `${String(globalLineCounter).padStart(6)}\t`; + globalLineCounter++; + } + + process.stdout.write(`${prefix}${line}\n`); + }); + } catch (error) { + console.error(`cat: ${filePath}: ${error.message}`); + } +} + +function main() { + 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); + } + }); + + if (files.length === 0) { + console.log("cat: missing file operand"); + process.exit(1); + } + + files.forEach((file) => { + printFile(file, options); + }); +} + +main(); diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 000000000..c4b5e88b7 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,57 @@ +#!/usr/bin/env node +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("."), + ); + + 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}`); + process.exitCode = 1; + } +} +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 + 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()]; + } + 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}`); + process.exitCode = 1; + } + }); +} +main(); diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100755 index 000000000..1f7623f02 --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,74 @@ +#!/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); + + 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); + } + //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); + + // 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}`); + process.exitCode = 1; + } + }); + + // If multiple files, print total counts + if (multipleFiles) { + printCounts("total", totalCounts, options); + } +} +main();