diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 00000000..562074af --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,40 @@ +const { promises: fs } = require("fs"); +const { program } = require("commander"); + +program + .name("cat") + .description("Implement a version of the cat program") + .option("-n, --number", "Number all output lines, starting at 1") + .option("-b, --number2", "Number non-empty output lines, starting at 1") + .argument("", "The file paths to process") + .parse(); + +const { number: nOption, number2: bOption } = program.opts(); +const paths = program.args; + +let lineNumber = 1; +let nonEmptyLineNumber = 1; + +function printLinesWithOptions(lines) { + lines.forEach(line => { + if (nOption) { + console.log(`${String(lineNumber++).padStart(6)} ${line}`); + } else if (bOption && line.trim()) { + console.log(`${String(nonEmptyLineNumber++).padStart(6)} ${line}`); + } else { + console.log(line); + } + }); +} + +async function readFileContent(path) { + try { + const content = await fs.readFile(path, "utf-8"); + const lines = content.split("\n"); + printLinesWithOptions(lines); + } catch (err) { + console.error(`Error reading file ${path}: ${err.message}`); + } +} + +Promise.all(paths.map(readFileContent)); diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 00000000..d05c6c2d --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,39 @@ +const fs = require('fs'); +const { program } = require('commander'); + +program + .option('-a, --all', 'Show hidden files') + .option('-1', 'Show one file per line') + .argument('[dirPaths...]', 'Directory paths', ['.']) // Support multiple directories + .parse(process.argv); + +const { all, '1': onePerLine } = program.opts(); +const dirPaths = program.args; + +function listDirectoryContents(dirPath, showHidden = false, onePerLine = false) { + fs.readdir(dirPath, (err, files) => { + if (err) { + console.error(`Error reading directory '${dirPath}': ${err.message}`); + return; + } + + if (!showHidden) { + files = files.filter(file => !file.startsWith('.')); + } + + console.log(`\n${dirPath}:`); + if (onePerLine) { + files.forEach(file => console.log(file)); + } else { + console.log(files.join(' ')); + } + }); +} + +dirPaths.forEach(dirPath => { + if (typeof dirPath !== 'string') { + console.error(`Error: Invalid directory path '${dirPath}'`); + return; + } + listDirectoryContents(dirPath, all, onePerLine); +}); diff --git a/implement-shell-tools/package-lock.json b/implement-shell-tools/package-lock.json new file mode 100644 index 00000000..8b77de79 --- /dev/null +++ b/implement-shell-tools/package-lock.json @@ -0,0 +1,20 @@ +{ + "name": "implement-shell-tools", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "commander": "^11.0.0" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + } + } +} diff --git a/implement-shell-tools/package.json b/implement-shell-tools/package.json new file mode 100644 index 00000000..8dbeb022 --- /dev/null +++ b/implement-shell-tools/package.json @@ -0,0 +1,7 @@ +{ + "type": "commonjs", + "dependencies": { + "commander": "^11.0.0" + } + } + \ 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 100644 index 00000000..1f7a3c78 --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,71 @@ +const { promises: fs } = require('fs'); +const path = require('path'); +const { program } = require('commander'); + +program + .description('Count lines, words, and characters in the specified files') + .option('-l, --lines', 'Count the lines') + .option('-w, --words', 'Count the words') + .option('-c, --characters', 'Count the characters') + .parse(process.argv); + +const options = program.opts(); +const files = program.args; + +if (files.length === 0) { + console.error("Error: No files provided."); + process.exit(1); +} + +async function countFileStats(filePath, options) { + try { + const data = await fs.readFile(filePath, 'utf8'); + + const lines = (data.match(/\n/g) || []).length; + const words = data.split(/\s+/).filter(Boolean).length; + const characters = data.length; + + const result = { lines, words, characters }; + const output = []; + if (options.lines) output.push(result.lines); + if (options.words) output.push(result.words); + if (options.characters) output.push(result.characters); + if (output.length === 0) output.push(result.lines, result.words, result.characters); + + return { file: filePath, output: output.join(' '), lines, words, characters }; + } catch (err) { + console.error(`Error reading file: ${filePath}`); + return null; + } +} + +async function processFiles() { + let totalLines = 0; + let totalWords = 0; + let totalCharacters = 0; + + for (const file of files) { + const filePath = path.resolve(file); + const result = await countFileStats(filePath, options); + + if (result) { + console.log(result.output, file); + totalLines += result.lines; + totalWords += result.words; + totalCharacters += result.characters; + } + } + + if (files.length > 1) { + const totals = []; + if (options.lines) totals.push(totalLines); + if (options.words) totals.push(totalWords); + if (options.characters !== false) totals.push(totalCharacters); + + if (totals.length > 0) { + console.log(totals.join(' ') + ' total'); + } + } +} + +processFiles();