diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100755 index 000000000..ed2360d49 --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,63 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +function cat(files, options) { + let lineNumber = 1; + + files.forEach((file) => { + try { + const data = fs.readFileSync(file, 'utf8'); + const lines = data.split('\n'); + + lines.forEach((line) => { + let prefix = ''; + const shouldNumberLine = options.numberLines || (options.numberNonEmpty && line.trim()); + if (shouldNumberLine) { + prefix = `${lineNumber}\t`; + lineNumber++; + } + console.log(`${prefix}${line}`); + }); + } catch (err) { + if (err.code === 'ENOENT') { + console.error(`cat: ${file}: No such file or directory`); + } else if (err.code === 'EACCES') { + console.error(`cat: ${file}: Permission denied`); + } else { + console.error(`cat: ${file}: An error occurred`); + } + process.exit(1); + } + }); +} + +function main() { + const args = process.argv.slice(2); + const options = { + numberLines: false, + numberNonEmpty: false, + }; + + const files = []; + + args.forEach((arg) => { + if (arg === '-n') { + options.numberLines = true; + } else if (arg === '-b') { + options.numberNonEmpty = true; + } else { + files.push(arg); + } + }); + + if (files.length === 0) { + console.error('Usage: node cat.js [-n | -b] ...'); + process.exit(1); + } + + cat(files, options); +} + +main(); \ No newline at end of file diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 000000000..b2587e30d --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,48 @@ +#!/usr/bin/env node + +const fs = require('fs'); + +function listFiles(directory, options) { + try { + const files = fs.readdirSync(directory, { withFileTypes: true }); + + files.forEach((file) => { + if (!options.all && file.name.startsWith('.')) { + return; // Skip hidden files unless -a is specified + } + console.log(`${directory}/${file.name}`); + }); + } catch (err) { + console.error(`ls: cannot access '${directory}': ${err.code === 'ENOENT' ? 'No such file or directory' : 'An error occurred'}`); + process.exit(1); + } +} + +function main() { + const args = process.argv.slice(2); + const options = { + all: false, + }; + + const directories = []; + + args.forEach((arg) => { + if (arg === '-1') { + // -1 is the default behavior, so no action needed + } else if (arg === '-a') { + options.all = true; + } else { + directories.push(arg); + } + }); + + if (directories.length === 0) { + directories.push('.'); + } + + directories.forEach((directory) => { + listFiles(directory, options); + }); +} + +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 100644 index 000000000..4f4602e1b --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,109 @@ +#!/usr/bin/env node + +const fs = require('fs'); + +function countFile(file) { + try { + const data = fs.readFileSync(file, 'utf8'); + + const lines = (data.match(/\n/g) || []).length; + const words = data.split(/\s+/).filter(Boolean).length; + const bytes = Buffer.byteLength(data, 'utf8'); + + return { lines, words, bytes }; + } catch (err) { + console.error(`wc: ${file}: ${err.code === 'ENOENT' ? 'No such file or directory' : 'An error occurred'}`); + process.exit(1); + } +} + +function selectedKeys(options) { + const keys = []; + if (options.lines) keys.push('lines'); + if (options.words) keys.push('words'); + if (options.bytes) keys.push('bytes'); + return keys; +} + +function valuesForKeys(counts, keys) { + return keys.map((key) => counts[key]); +} + +function printRows(rows, keys) { + const alignColumns = keys.length > 1 || rows.length > 1; + + if (!alignColumns) { + const [values, name] = rows[0]; + console.log(`${values[0]} ${name}`); + return; + } + + const widths = keys.map((_, index) => { + const maxLen = Math.max(...rows.map(([values]) => String(values[index]).length)); + return Math.max(3, maxLen); + }); + + rows.forEach(([values, name]) => { + const formattedValues = values + .map((value, index) => String(value).padStart(widths[index], ' ')) + .join(' '); + console.log(`${formattedValues} ${name}`); + }); +} + +function main() { + const args = process.argv.slice(2); + const options = { + lines: false, + words: false, + bytes: false, + }; + + const files = []; + + args.forEach((arg) => { + if (arg === '-l') { + options.lines = true; + } else if (arg === '-w') { + options.words = true; + } else if (arg === '-c') { + options.bytes = true; + } else { + files.push(arg); + } + }); + + if (!options.lines && !options.words && !options.bytes) { + options.lines = true; + options.words = true; + options.bytes = true; + } + + if (files.length === 0) { + console.error('Usage: wc [-l | -w | -c] ...'); + process.exit(1); + } + + let totalLines = 0; + let totalWords = 0; + let totalBytes = 0; + const keys = selectedKeys(options); + const rows = []; + + files.forEach((file) => { + const counts = countFile(file); + const { lines, words, bytes } = counts; + totalLines += lines; + totalWords += words; + totalBytes += bytes; + rows.push([valuesForKeys(counts, keys), file]); + }); + + if (files.length > 1) { + rows.push([valuesForKeys({ lines: totalLines, words: totalWords, bytes: totalBytes }, keys), 'total']); + } + + printRows(rows, keys); +} + +main(); \ No newline at end of file