» Node.js创建命令行程序grep » 2. 开发 » 2.8 支持多文件

支持多文件

如果想实现如下的多文件使用方式,需要再次调整 yargs 解析逻辑。

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

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

注意:
在命令行中直接使用 *.txt 作为参数可能不会一定按你的预期工作,因为通配符模式(如 *.txt)的展开是由 shell(例如 Bash)展开的。 Node.js 中的 yargs 模块不会自动执行此展开。不过,你可以使用 glob 包手动展开。

添加多路径版本的 greplib/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
}

修改 yargs 部分,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)

重新编译后可执行如下操作:

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

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

结果:

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);

赞👍🏻!顺顺当当!

上页下页