diff --git a/implement-shell-tools/cat/my_cat.js b/implement-shell-tools/cat/my_cat.js new file mode 100644 index 00000000..b23b575f --- /dev/null +++ b/implement-shell-tools/cat/my_cat.js @@ -0,0 +1,45 @@ +import { promises as fs } from 'node:fs'; +import { argv } from 'node:process'; + +const args = argv.slice(2); // skip node and script name + +let showLineNumb = false; +let showNoBlankNumb = false +const files = []; + + +args.forEach(arg => { + if (arg === '-n') { + showLineNumb = true; + } else if (arg === '-b') { + showNoBlankNumb = true; + }else { + files.push(arg); + } +}); + +if (files.length === 0) { + console.error('No input file specified'); + process.exit(1); +} + +// Global counters across all files +let counter = 1; + +for (const file of files) { + try { + let content = await fs.readFile(file, 'utf-8'); + const lines = content.split('\n'); + + if (showNoBlankNumb) { // Number only non-blank lines + content = lines.map(line => + line.trim() === '' ? '' : `${counter++}\t${line}` + ).join('\n'); + } else if (showLineNumb) { // Number all lines + content = lines.map((line) => `${counter++}\t${line}`).join('\n'); + } + console.log(content); + } catch (err) { + console.error(`Cannot access '${file}': ${err.message}`); + } +} \ No newline at end of file diff --git a/implement-shell-tools/ls/my_ls.js b/implement-shell-tools/ls/my_ls.js new file mode 100644 index 00000000..558499ba --- /dev/null +++ b/implement-shell-tools/ls/my_ls.js @@ -0,0 +1,32 @@ +import { readdirSync } from 'node:fs'; +import { argv } from 'node:process'; + +const args = argv.slice(2); // skips node and script name + +const flags = args.filter(arg => arg.startsWith('-')); +const operands = args.filter(arg => !arg.startsWith('-')); + +let showAll = flags.includes('-a'); +let onePerLine = flags.includes('-1'); + +if (operands.length === 0) operands.push('.'); // defaults to current dir + +operands.forEach(dir => { + try { + let files = readdirSync(dir); // Reads the contents of the dir synchronously (readdirSync) + + if (!showAll) { + files = files.filter(file => !file.startsWith('.')); + } + + files.sort((a, b) => a.localeCompare(b)); + + if (onePerLine) { + files.forEach(file => console.log(file)); + } else { + console.log(files.join(' ')); + } + } catch (err) { + console.error(`ls: cannot access '${dir}': ${err.message}`); + } +}); \ No newline at end of file diff --git a/implement-shell-tools/wc/my_wc.js b/implement-shell-tools/wc/my_wc.js new file mode 100644 index 00000000..35c77e32 --- /dev/null +++ b/implement-shell-tools/wc/my_wc.js @@ -0,0 +1,96 @@ +import { promises as fs } from 'node:fs'; +import { argv } from 'node:process'; + +const args = argv.slice(2); + +if (args.length === 0) { + console.error('Usage: node my_wc.mjs [-l] [-w] [-c] [file2 ...]'); + process.exit(1); +} + +// Parse flags and files +const flags = { + l: false, + w: false, + c: false, +}; + +const files = []; + +for (const arg of args) { + if (arg.startsWith('-') && arg.length > 1) { + for (const ch of arg.slice(1)) { + if (flags.hasOwnProperty(ch)) { + flags[ch] = true; + } else { + console.error(`Unknown option: -${ch}`); + process.exit(1); + } + } + } else { + files.push(arg); + } +} + +if (files.length === 0) { + console.error('Error: no files specified.'); + process.exit(1); +} + +// If no flags specified, show all by default +const showLines = flags.l || (!flags.l && !flags.w && !flags.c); +const showWords = flags.w || (!flags.l && !flags.w && !flags.c); +const showBytes = flags.c || (!flags.l && !flags.w && !flags.c); + +function pad(num, width = 8) { + return num.toString().padStart(width, ' '); +} + +function formatOutput({ lines, words, bytes }, label) { + let output = ''; + if (showLines) output += pad(lines); + if (showWords) output += pad(words); + if (showBytes) output += pad(bytes); + output += ` ${label}`; + return output; +} + +async function countFile(file) { + try { + const content = await fs.readFile(file, 'utf-8'); + const lines = content.split('\n').length - 1; + const words = content.trim().split(/\s+/).filter(Boolean).length; + const bytes = Buffer.byteLength(content, 'utf-8'); + + const counts = { lines, words, bytes }; + console.log(formatOutput(counts, file)); + + return counts; + } catch (err) { + console.error(`Cannot access '${file}': ${err.message}`); + return null; + } +} + +async function main() { + // Instead of .map, I can explicitly collect promises using forEach(): + const promises = []; + files.forEach(file => { + promises.push(countFile(file)); + }); + const counts = await Promise.all(promises); + + const validCounts = counts.filter(c => c !== null); + + if (validCounts.length > 1) { + const totals = { + lines: validCounts.reduce((sum, c) => sum + c.lines, 0), + words: validCounts.reduce((sum, c) => sum + c.words, 0), + bytes: validCounts.reduce((sum, c) => sum + c.bytes, 0), + } + + console.log(formatOutput(totals, 'total')); + } +} + +main();