» Make grep CLI App in Node.js » 2. Development » 2.8 Support Multiple File Paths

Support Multiple File Paths

If you want to support multiple file paths like this, you need to tune yargs parsing logic again.

node dist/bin/cmd.js pattern file1.txt file2.txt file3.txt

# Or
node dist/bin/cmd.js pattern *.txt

Note:
Using *.txt as an argument in the command line directly may not work as expected because the shell (e.g., Bash) is responsible for expanding wildcard patterns like *.txt. The yargs package in Node.js won't automatically perform this expansion.
However, you can use the glob package to manually expand the wildcard pattern.

Add multiple paths version grep in lib/grep.ts:

export async function grepMulti (pattern: string, filePaths: string[], options: Options): Promise<MatchResult> {
  if (filePaths.length === 0) {
    return await grep(pattern, '', options)
  }
  const results: MatchResult = {}
  for (const filePath of filePaths) {
    const result = await grep(pattern, filePath, options)
    for (const k in result) {
      results[k] = result[k]
    }
  }
  return results
}

export async function grepRecursiveMulti (pattern: string, dirPaths: string[], options: Options): Promise<MatchResult> {
  const results: MatchResult = {}
  for (const dirPath of dirPaths) {
    const result = await grepRecursive(pattern, dirPath, options)
    for (const k in result) {
      results[k] = result[k]
    }
  }
  return results
}

Modify the yargs part in bin/cmd.ts:

 import yargs from 'yargs'
 
-import { grep, grepCount, grepRecursive } from '../lib/grep.js'
+import { grepMulti, grepCount, grepRecursiveMulti } from '../lib/grep.js'
 import type { MatchResult } from '../lib/grep.js'
 
 // Parse command-line options
@@ -51,7 +51,7 @@ const argv = await yargs(process.argv.slice(2))
   .demandCommand(1, 'Please provide pattern to search for.').argv
 
 const pattern = argv._[0] as string
-const filePath = argv._[1] !== undefined ? argv._[1] as string : ''
+const filePaths = argv._.slice(1) as string[]
 
 if (argv.help as boolean) {
   // Print help message and exit
@@ -63,9 +63,9 @@ const options = {
   ignoreCase: argv['ignore-case'] as boolean,
   invertMatch: argv['invert-match'] as boolean
 }
-const result = (argv.recursive as boolean && filePath !== '')
-  ? grepRecursive(pattern, filePath, options)
-  : grep(pattern, filePath, options)
+const result = (argv.recursive as boolean && filePaths.length !== 0)
+  ? grepRecursiveMulti(pattern, filePaths, options)
+  : grepMulti(pattern, filePaths, options)

After re-compilation, you can do this now:

node dist/bin/cmd.js -in line dist/*/*

# Or
node dist/bin/cmd.js -in line lib/grep.ts bin/cmd.ts

Result:

dist/bin/cmd.js:
4: // Parse command-line options
10: describe: 'Only a count of selected lines is written to standard output.',
27: alias: 'line-number',
28: describe: 'Each output line is preceded by its relative line number in the file, starting at line 1. The line number counter is reset for each file processed. This option is ignored if -c is specified.',
40: describe: 'Selected lines are those not matching any of the specified patterns.',
65: printResult(result, argv['line-number']);
71: function printResult(result, showLineNumber) {
74: for (const [filePath, lines] of Object.entries(result)) {
75: for (const [lineNumber, line] of lines) {
80: if (showLineNumber) {
81: console.log(`${lineNumber}: ${line}`);
84: console.log(line);

dist/lib/grep.js:
3: import * as readline from 'readline';
6: const lines = filePath === '' ? await _readStdinLines() : await _readFileLines(filePath);
9: let matchingLines;
11: matchingLines = _filterLines(regex, lines, false);
14: matchingLines = _filterLines(regex, lines, true);
16: return { [filePath]: matchingLines };
60: return Object.values(result).reduce((count, lines) => count + lines.length, 0);
62: function _filterLines(regexPattern, lines, flag) {
63: const candidates = lines.map((line, index) => [index + 1, line.trim()]);
65: .filter(([_, line]) => regexPattern.test(line) === flag);
67: async function _readFileLines(filePath) {
82: async function _readStdinLines() {
89: const lines = [];
90: rl.on('line', (line) => {
94: resolve(lines);

Nice👍🏻! So far so good!