» Node.js创建命令行程序grep » 2. 开发 » 2.7 支持管道

支持管道

在类 Unix 操作系统(如 Linux 和 macOS)中,管道是一种用于进程间通信(IPC)的机制,允许一个进程的输出被用作另一个进程的输入。

在 Shell 命令中,管道由 | 符号表示。当你使用类似 command1 | command2 的命令时,意味着 command1 的标准输出被连接到 command2 的标准输入。

例如:

cat file.txt | grep "pattern"

在此命令中,file.txt 的内容被作为输入传递到 grep 命令,该命令搜索指定的模式。

修改 bin/cmd.ts 以使 file 参数变为可选:

 // Parse command-line options
 const argv = await yargs(process.argv.slice(2))
   .locale('en')
-  .usage('Usage: $0 [options] <pattern> <file>')
+  .usage('Usage: $0 [options] <pattern> [<file>]')
   .option('c', {
     alias: 'count',
     describe: 'Only a count of selected lines is written to standard output.',
@@ -48,10 +48,10 @@ const argv = await yargs(process.argv.slice(2))
     type: 'boolean',
     default: false
   })
-  .demandCommand(2, 'Please provide both pattern and file arguments.').argv
+  .demandCommand(1, 'Please provide pattern to search for.').argv
 
 const pattern = argv._[0] as string
-const filePath = argv._[1] as string
+const filePath = argv._[1] !== undefined ? argv._[1] as string : ''

这使得该参数变得可选,允许命令行为其提供一个值或者不提供。

文件路径为空时无法做递归搜索,故修改 bin/cmd.ts:

-const result = argv.recursive as boolean
+const result = (argv.recursive as boolean && filePath !== '')
   ? grepRecursive(pattern, filePath, options)
   : grep(pattern, filePath, options)

lib/grep.ts 中,当 filePath 为空时,从标准输入读取内容:

 export async function grep (pattern: string, filePath: string, options: Options): Promise<MatchResult> {
   const { ignoreCase, invertMatch } = options
-  const lines = await _readFileLines(filePath)
+  const lines = filePath === '' ? await _readStdinLines() : await _readFileLines(filePath)
   const regexFlags = ignoreCase ? 'gi' : 'g'
   const regex = new RegExp(pattern, regexFlags)
   let matchingLines: MatchItem[]
@@ -65,3 +66,22 @@ async function _readFileLines (filePath: string): Promise<string[]> {
   }
   return []
 }
+
+async function _readStdinLines (): Promise<string[]> {
+  const rl = readline.createInterface({
+    input: process.stdin,
+    output: process.stdout,
+    terminal: false
+  })
+
+  return await new Promise((resolve) => {
+    const lines: string[] = []
+    rl.on('line', (line) => {
+      lines.push(line)
+    })
+
+    rl.on('close', () => {
+      resolve(lines)
+    })
+  })
+}

重新编译后,现在可以执行以下操作:

cat lib/grep.ts | node dist/bin/cmd.js -in line

其结果:

3: import * as readline from 'readline'
16: const lines = filePath === '' ? await _readStdinLines() : await _readFileLines(filePath)
19: let matchingLines: MatchItem[]
21: matchingLines = _filterLines(regex, lines, false)
23: matchingLines = _filterLines(regex, lines, true)
25: return { [filePath]: matchingLines }
48: (count, lines) => count + lines.length,
53: function _filterLines (regexPattern: RegExp, lines: string[], flag: boolean): MatchItem[] {
54: const candidates: MatchItem[] = lines.map((line, index) => [index + 1, line.trim()])
56: .filter(([_, line]) => regexPattern.test(line) === flag)
59: async function _readFileLines (filePath: string): Promise<string[]> {
70: async function _readStdinLines (): Promise<string[]> {
78: const lines: string[] = []
79: rl.on('line', (line) => {
84: resolve(lines)
上页下页