From 1576d23673c905182f65c30e60b937f6f83676b6 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Sat, 21 Mar 2026 12:22:03 +0000 Subject: [PATCH 1/7] complete cat excercise --- implement-shell-tools/cat/cat.js | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100755 implement-shell-tools/cat/cat.js diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js new file mode 100755 index 000000000..5005eed7e --- /dev/null +++ b/implement-shell-tools/cat/cat.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +function cat(files, options) { + let lineNumber = 1; + + files.forEach((file) => { + const filePath = path.resolve(file); + + try { + const data = fs.readFileSync(filePath, 'utf8'); + const lines = data.split('\n'); + + lines.forEach((line) => { + if (options.numberNonEmpty && line.trim()) { + console.log(`${lineNumber}\t${line}`); + lineNumber++; + } else if (options.numberLines) { + console.log(`${lineNumber}\t${line}`); + lineNumber++; + } else { + console.log(line); + } + }); + } catch (err) { + console.error(`cat: ${file}: No such file or directory`); + } + }); +} + +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 From 34fe1d09e26f469a2df3c27364a6d37b4aff91b3 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Sat, 21 Mar 2026 12:43:06 +0000 Subject: [PATCH 2/7] complete ls exercise --- implement-shell-tools/ls/ls.js | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 implement-shell-tools/ls/ls.js diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js new file mode 100644 index 000000000..056213466 --- /dev/null +++ b/implement-shell-tools/ls/ls.js @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +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(file.name); + }); + } catch (err) { + console.error(`ls: cannot access '${directory}': No such file or directory`); + } +} + +function main() { + const args = process.argv.slice(2); + const options = { + all: false, + }; + + let 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 = [arg]; + } + }); + + directories.forEach((directory) => { + listFiles(directory, options); + }); +} + +main(); \ No newline at end of file From daac5ecd310b8071a20a65a51ade077651e945fb Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Sat, 21 Mar 2026 13:19:53 +0000 Subject: [PATCH 3/7] complete wc exercise --- implement-shell-tools/wc/wc.js | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 implement-shell-tools/wc/wc.js diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js new file mode 100644 index 000000000..60171cf31 --- /dev/null +++ b/implement-shell-tools/wc/wc.js @@ -0,0 +1,61 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +function countFile(filePath, options) { + try { + const data = fs.readFileSync(filePath, 'utf8'); + + const lines = data.split('\n').length; + const words = data.split(/\s+/).filter(Boolean).length; + const bytes = Buffer.byteLength(data, 'utf8'); + + if (options.lines) { + console.log(`${lines}\t${filePath}`); + } else if (options.words) { + console.log(`${words}\t${filePath}`); + } else if (options.bytes) { + console.log(`${bytes}\t${filePath}`); + } else { + console.log(`${lines}\t${words}\t${bytes}\t${filePath}`); + } + } catch (err) { + console.error(`wc: ${filePath}: No such file or directory`); + } +} + +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 (files.length === 0) { + console.error('Usage: wc [-l | -w | -c] ...'); + process.exit(1); + } + + files.forEach((file) => { + const filePath = path.resolve(file); + countFile(filePath, options); + }); +} + +main(); \ No newline at end of file From 7e70d1cc7d2a35a990362b4025ab7a3471167bb8 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Sun, 5 Apr 2026 21:56:25 +0100 Subject: [PATCH 4/7] Refactor file handling and error messages in cat, ls, and wc tools --- implement-shell-tools/cat/cat.js | 25 ++++++++++++------------- implement-shell-tools/ls/ls.js | 14 +++++++++----- implement-shell-tools/wc/wc.js | 26 +++++++++++--------------- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 5005eed7e..287b29148 100755 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -7,25 +7,24 @@ function cat(files, options) { let lineNumber = 1; files.forEach((file) => { - const filePath = path.resolve(file); - try { - const data = fs.readFileSync(filePath, 'utf8'); + const data = fs.readFileSync(file, 'utf8'); const lines = data.split('\n'); lines.forEach((line) => { - if (options.numberNonEmpty && line.trim()) { - console.log(`${lineNumber}\t${line}`); - lineNumber++; - } else if (options.numberLines) { - console.log(`${lineNumber}\t${line}`); - lineNumber++; - } else { - console.log(line); - } + const prefix = options.numberNonEmpty && line.trim() ? `${lineNumber}\t` : options.numberLines ? `${lineNumber}\t` : ''; + console.log(`${prefix}${line}`); + if (prefix) lineNumber++; }); } catch (err) { - console.error(`cat: ${file}: No such file or directory`); + 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); } }); } diff --git a/implement-shell-tools/ls/ls.js b/implement-shell-tools/ls/ls.js index 056213466..b2587e30d 100644 --- a/implement-shell-tools/ls/ls.js +++ b/implement-shell-tools/ls/ls.js @@ -1,7 +1,6 @@ #!/usr/bin/env node const fs = require('fs'); -const path = require('path'); function listFiles(directory, options) { try { @@ -11,10 +10,11 @@ function listFiles(directory, options) { if (!options.all && file.name.startsWith('.')) { return; // Skip hidden files unless -a is specified } - console.log(file.name); + console.log(`${directory}/${file.name}`); }); } catch (err) { - console.error(`ls: cannot access '${directory}': No such file or directory`); + console.error(`ls: cannot access '${directory}': ${err.code === 'ENOENT' ? 'No such file or directory' : 'An error occurred'}`); + process.exit(1); } } @@ -24,7 +24,7 @@ function main() { all: false, }; - let directories = ['.']; + const directories = []; args.forEach((arg) => { if (arg === '-1') { @@ -32,10 +32,14 @@ function main() { } else if (arg === '-a') { options.all = true; } else { - directories = [arg]; + directories.push(arg); } }); + if (directories.length === 0) { + directories.push('.'); + } + directories.forEach((directory) => { listFiles(directory, options); }); diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index 60171cf31..e807112ce 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -1,27 +1,24 @@ #!/usr/bin/env node const fs = require('fs'); -const path = require('path'); -function countFile(filePath, options) { +function countFile(file, options) { try { - const data = fs.readFileSync(filePath, 'utf8'); + const data = fs.readFileSync(file, 'utf8'); const lines = data.split('\n').length; const words = data.split(/\s+/).filter(Boolean).length; const bytes = Buffer.byteLength(data, 'utf8'); - if (options.lines) { - console.log(`${lines}\t${filePath}`); - } else if (options.words) { - console.log(`${words}\t${filePath}`); - } else if (options.bytes) { - console.log(`${bytes}\t${filePath}`); - } else { - console.log(`${lines}\t${words}\t${bytes}\t${filePath}`); - } + const results = []; + if (options.lines) results.push(lines); + if (options.words) results.push(words); + if (options.bytes) results.push(bytes); + + console.log(`${results.join('\t')}\t${file}`); } catch (err) { - console.error(`wc: ${filePath}: No such file or directory`); + console.error(`wc: ${file}: ${err.code === 'ENOENT' ? 'No such file or directory' : 'An error occurred'}`); + process.exit(1); } } @@ -53,8 +50,7 @@ function main() { } files.forEach((file) => { - const filePath = path.resolve(file); - countFile(filePath, options); + countFile(file, options); }); } From a3dc8ae4141ef0ba56bc36a1ae406bc25615ce5c Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Fri, 10 Apr 2026 22:38:57 +0100 Subject: [PATCH 5/7] Enhance cat and wc tools: improve line numbering logic and aggregate results --- implement-shell-tools/cat/cat.js | 7 ++++++- implement-shell-tools/wc/wc.js | 26 ++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 287b29148..eb092f348 100755 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -12,7 +12,12 @@ function cat(files, options) { const lines = data.split('\n'); lines.forEach((line) => { - const prefix = options.numberNonEmpty && line.trim() ? `${lineNumber}\t` : options.numberLines ? `${lineNumber}\t` : ''; + let prefix = ''; + if (options.numberNonEmpty && line.trim()) { + prefix = `${lineNumber}\t`; + } else if (options.numberLines) { + prefix = `${lineNumber}\t`; + } console.log(`${prefix}${line}`); if (prefix) lineNumber++; }); diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index e807112ce..d8f274665 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -11,11 +11,13 @@ function countFile(file, options) { const bytes = Buffer.byteLength(data, 'utf8'); const results = []; - if (options.lines) results.push(lines); - if (options.words) results.push(words); - if (options.bytes) results.push(bytes); + if (options.lines || (!options.lines && !options.words && !options.bytes)) results.push(lines); + if (options.words || (!options.lines && !options.words && !options.bytes)) results.push(words); + if (options.bytes || (!options.lines && !options.words && !options.bytes)) results.push(bytes); console.log(`${results.join('\t')}\t${file}`); + + return { lines, words, bytes }; } catch (err) { console.error(`wc: ${file}: ${err.code === 'ENOENT' ? 'No such file or directory' : 'An error occurred'}`); process.exit(1); @@ -49,9 +51,25 @@ function main() { process.exit(1); } + let totalLines = 0; + let totalWords = 0; + let totalBytes = 0; + files.forEach((file) => { - countFile(file, options); + const { lines, words, bytes } = countFile(file, options); + totalLines += lines; + totalWords += words; + totalBytes += bytes; }); + + if (files.length > 1) { + const totalResults = []; + if (options.lines || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalLines); + if (options.words || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalWords); + if (options.bytes || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalBytes); + + console.log(`${totalResults.join('\t')}\ttotal`); + } } main(); \ No newline at end of file From 9f4f7aa5da47da0cdd1cefec8bab8bea3cd3cbb8 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Wed, 15 Apr 2026 22:57:08 +0100 Subject: [PATCH 6/7] Refactor cat and wc tools: improve line handling and output formatting --- implement-shell-tools/cat/cat.js | 22 +++++++---- implement-shell-tools/wc/wc.js | 66 ++++++++++++++++++++++++-------- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index eb092f348..855b51da7 100755 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,7 +1,6 @@ #!/usr/bin/env node const fs = require('fs'); -const path = require('path'); function cat(files, options) { let lineNumber = 1; @@ -10,16 +9,23 @@ function cat(files, options) { try { const data = fs.readFileSync(file, 'utf8'); const lines = data.split('\n'); + const hasTrailingNewline = data.endsWith('\n'); + + lines.forEach((line, index) => { + const isVirtualTrailingLine = hasTrailingNewline && index === lines.length - 1; + if (isVirtualTrailingLine) { + return; + } - lines.forEach((line) => { let prefix = ''; - if (options.numberNonEmpty && line.trim()) { - prefix = `${lineNumber}\t`; - } else if (options.numberLines) { - prefix = `${lineNumber}\t`; + const shouldNumberLine = options.numberLines || (options.numberNonEmpty && line.length > 0); + if (shouldNumberLine) { + prefix = `${String(lineNumber).padStart(6)}\t`; + lineNumber++; } - console.log(`${prefix}${line}`); - if (prefix) lineNumber++; + + const shouldAppendNewline = hasTrailingNewline || index < lines.length - 1; + process.stdout.write(`${prefix}${line}${shouldAppendNewline ? '\n' : ''}`); }); } catch (err) { if (err.code === 'ENOENT') { diff --git a/implement-shell-tools/wc/wc.js b/implement-shell-tools/wc/wc.js index d8f274665..4f4602e1b 100644 --- a/implement-shell-tools/wc/wc.js +++ b/implement-shell-tools/wc/wc.js @@ -2,21 +2,14 @@ const fs = require('fs'); -function countFile(file, options) { +function countFile(file) { try { const data = fs.readFileSync(file, 'utf8'); - const lines = data.split('\n').length; + const lines = (data.match(/\n/g) || []).length; const words = data.split(/\s+/).filter(Boolean).length; const bytes = Buffer.byteLength(data, 'utf8'); - const results = []; - if (options.lines || (!options.lines && !options.words && !options.bytes)) results.push(lines); - if (options.words || (!options.lines && !options.words && !options.bytes)) results.push(words); - if (options.bytes || (!options.lines && !options.words && !options.bytes)) results.push(bytes); - - console.log(`${results.join('\t')}\t${file}`); - return { lines, words, bytes }; } catch (err) { console.error(`wc: ${file}: ${err.code === 'ENOENT' ? 'No such file or directory' : 'An error occurred'}`); @@ -24,6 +17,40 @@ function countFile(file, options) { } } +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 = { @@ -46,6 +73,12 @@ function main() { } }); + 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); @@ -54,22 +87,23 @@ function main() { let totalLines = 0; let totalWords = 0; let totalBytes = 0; + const keys = selectedKeys(options); + const rows = []; files.forEach((file) => { - const { lines, words, bytes } = countFile(file, options); + 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) { - const totalResults = []; - if (options.lines || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalLines); - if (options.words || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalWords); - if (options.bytes || (!options.lines && !options.words && !options.bytes)) totalResults.push(totalBytes); - - console.log(`${totalResults.join('\t')}\ttotal`); + rows.push([valuesForKeys({ lines: totalLines, words: totalWords, bytes: totalBytes }, keys), 'total']); } + + printRows(rows, keys); } main(); \ No newline at end of file From 054db4143497707aed0d725c46264a78e7381252 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Wed, 15 Apr 2026 22:58:05 +0100 Subject: [PATCH 7/7] Refactor cat tool: simplify line handling and improve output formatting --- implement-shell-tools/cat/cat.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/implement-shell-tools/cat/cat.js b/implement-shell-tools/cat/cat.js index 855b51da7..ed2360d49 100755 --- a/implement-shell-tools/cat/cat.js +++ b/implement-shell-tools/cat/cat.js @@ -1,6 +1,7 @@ #!/usr/bin/env node const fs = require('fs'); +const path = require('path'); function cat(files, options) { let lineNumber = 1; @@ -9,23 +10,15 @@ function cat(files, options) { try { const data = fs.readFileSync(file, 'utf8'); const lines = data.split('\n'); - const hasTrailingNewline = data.endsWith('\n'); - - lines.forEach((line, index) => { - const isVirtualTrailingLine = hasTrailingNewline && index === lines.length - 1; - if (isVirtualTrailingLine) { - return; - } + lines.forEach((line) => { let prefix = ''; - const shouldNumberLine = options.numberLines || (options.numberNonEmpty && line.length > 0); + const shouldNumberLine = options.numberLines || (options.numberNonEmpty && line.trim()); if (shouldNumberLine) { - prefix = `${String(lineNumber).padStart(6)}\t`; + prefix = `${lineNumber}\t`; lineNumber++; } - - const shouldAppendNewline = hasTrailingNewline || index < lines.length - 1; - process.stdout.write(`${prefix}${line}${shouldAppendNewline ? '\n' : ''}`); + console.log(`${prefix}${line}`); }); } catch (err) { if (err.code === 'ENOENT') {