diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100644 index 0000000..2bdbfc0 --- /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 0000000..7ba9c99 --- /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 0000000..58de824 --- /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