如何从 node.js 打开终端应用程序?

Posted

技术标签:

【中文标题】如何从 node.js 打开终端应用程序?【英文标题】:How do I open a terminal application from node.js? 【发布时间】:2012-02-25 16:40:46 【问题描述】:

我希望能够从运行在终端的node.js程序打开Vim,创建一些内容,保存退出Vim,然后抓取文件的内容。

我正在尝试做这样的事情:

filename = '/tmp/tmpfile-' + process.pid

editor = process.env['EDITOR'] ? 'vi'
spawn editor, [filename], (err, stdout, stderr) ->

  text = fs.readFileSync filename
  console.log text

但是,当它运行时,它只是挂起终端。

我也用exec 尝试过,得到了同样的结果。

更新:

由于这个过程是从在readline 运行的提示符下键入的命令启动的,因此这很复杂。我将最新版本的相关部分完全提取到一个文件中。这是它的全部内容:

spawn = require 'child_process'
fs = require 'fs'
tty = require 'tty'
rl = require 'readline'

cli = rl.createInterface process.stdin, process.stdout, null
cli.prompt()

filename = '/tmp/tmpfile-' + process.pid

proc = spawn 'vim', [filename]

#cli.pause()
process.stdin.resume()

indata = (c) ->
    proc.stdin.write c
process.stdin.on 'data', indata

proc.stdout.on 'data', (c) ->
    process.stdout.write c

proc.on 'exit', () ->
    tty.setRawMode false
    process.stdin.removeListener 'data', indata

    # Grab content from the temporary file and display it
    text = fs.readFile filename, (err, data) ->
        throw err if err?  
        console.log data.toString()

        # Try to resume readline prompt
        cli.prompt()

它的工作方式如上所示,它会显示几秒钟的提示,然后启动到 Vim,但 TTY 被搞砸了。我可以编辑和保存文件,并且内容打印正确。退出时终端也打印了一堆垃圾,之后 Readline 功能被破坏(没有向上/向下箭头,没有 Tab 补全)。

如果我取消注释 cli.pause() 行,那么在 Vim 中 TTY 是正常的,但我卡在插入模式,Esc 键不起作用。如果我点击Ctrl-C,它将退出子进程和父进程。

【问题讨论】:

你能解释一下这个用例吗?你是想通过运行 Vim 命令来与 Vim 交互,还是只是将文件写入磁盘? 是否有意要求用户使用编辑器来创建内容?或者您打算完全从node.js 内部驱动vim 您可以尝试打开/关闭原始模式吗? nodejs.org/docs/latest/api/tty.html#tty.setRawMode 我似乎使用了错误的工具来完成这项工作:-) 这将是bash 中的一行。不过,将应用程序的其余部分放在 node.js 中,它工作得很好,而且我可以更轻松地在服务器 API 和这个客户端代码之间移动代码。我想这是一次很好的学习经历。 是的,在 node 中做这件事看起来确实很奇怪,但是弄清楚如何去做是很有趣的。我会看看能不能在一个小时内解决你的 readline 问题。 【参考方案1】:

你可以从主进程继承stdio。

const child_process = require('child_process')
var editor = process.env.EDITOR || 'vi';

var child = child_process.spawn(editor, ['/tmp/somefile.txt'], 
    stdio: 'inherit'
);

child.on('exit', function (e, code) 
    console.log("finished");
);

更多选项在这里:http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options

【讨论】:

这似乎是一个更新且简单得多的答案! 这是截至 2015 年底当前 nodejs 版本的正确解决方案。当前标记为“正确”的其他答案将不再有效。 我认为这是一个关于如何在一个class: gist.github.com/56c65f8608781dbd88ef 中执行此操作的示例 这对我有用,比接受的答案简单得多。 文档链接显示使用close 事件,但此答案显示使用exit 事件。哪个是正确的?【参考方案2】:

更新:我的答案在创建时应用,但对于现代版本的 Node,请查看 this other answer。

首先,您对 spawn 的使用不正确。这是文档。 http://nodejs.org/docs/latest/api/child_processes.html#child_process.spawn

您的示例代码看起来像是您希望 vim 自动弹出并接管终端,但它不会。需要记住的重要一点是,即使您可能会生成一个进程,您也必须确保来自该进程的数据能够传送到您的终端进行显示。

在这种情况下,你需要从标准输入中取出数据并发送给vim,并且你需要把vim输出的数据取出并设置到你的终端,否则你什么都看不到。您还需要将 tty 设置为 raw 模式,否则 node 会拦截一些键序列,因此 vim 将无法正常运行。

接下来,不要执行 readFileSync。如果您遇到认为需要使用同步方法的情况,那么很可能您做错了什么。

这是我整理的一个简单示例。我不能保证它适用于每一种情况,但它应该涵盖大多数情况。

var tty = require('tty');
var child_process = require('child_process');
var fs = require('fs');

function spawnVim(file, cb) 
  var vim = child_process.spawn( 'vim', [file])

  function indata(c) 
    vim.stdin.write(c);
  
  function outdata(c) 
    process.stdout.write(c);
  

  process.stdin.resume();
  process.stdin.on('data', indata);
  vim.stdout.on('data', outdata);
  tty.setRawMode(true);

  vim.on('exit', function(code) 
    tty.setRawMode(false);
    process.stdin.pause();
    process.stdin.removeListener('data', indata);
    vim.stdout.removeListener('data', outdata);

    cb(code);
  );


var filename = '/tmp/somefile.txt';

spawnVim(filename, function(code) 
  if (code == 0) 
    fs.readFile(filename, function(err, data) 
      if (!err) 
        console.log(data.toString());
      
    );
  
);

更新

我看到了。不幸的是,我不认为 readline 与您希望的所有这些兼容。问题是,当您创建接口时,节点类型假定它将从那时起完全控制该流。当我们将该数据重定向到 vim 时,readline 仍在处理按键,但 vim 也在做同样的事情。

我看到的唯一解决方法是在启动 vim 之前手动禁用 cli 界面中的所有内容。

就在你生成进程之前,我们需要关闭界面,不幸的是手动删除按键监听器,因为至少目前节点不会自动删除它。

process.stdin.removeAllListeners 'keypress'
cli.close()
tty.setRawMode true

然后在进程'exit'回调中,你需要再次调用createInterface。

【讨论】:

这几乎可以工作了。第一个问题是 Vim 需要几秒钟才能出现,而且它在终端中只是半屏。其次,TTY 也混乱了。在输入iThis is a test of the editor 之后显示TThi i tes o th editor。退出并显示文件内容会显示正确的字符串。 我在其中使用的程序是 API 的单用户命令行客户端,所以这就是我使用 readFileSync 的原因。通过删除回调来稍微简化代码。事实上,让所有代码同步可能会更简单......但这就是我现在所拥有的。 不过,readFileSync 是错误的形式。如果你要使用节点,你最好做对。我不知道为什么它对你来说如此奇怪。您可以尝试使用更简单的编辑器,例如 nano 吗?我的 vim 肯定比你的好,但我的退格键不起作用,我似乎无法让它工作,但它在 nano 中工作。您是否使用我发布的确切代码遇到了这个问题,或者您是否将其集成到您的代码中?也许发布更多您正在使用的代码? 好的,被指控有罪 ;-) 我之前只尝试过集成您的代码。我不知道为什么我不只是复制和粘贴并运行它。我现在这样做了,效果很好(这里没有退格问题)。这使我发现我对 readline 的使用显然导致了问题。我更新了原始问题。非常感谢您的帮助! 我没有阅读关于继承的较低答案,发现此代码 (1) 不再有效(节点 6.9.1)和 (2) 它可以通过删除 var tty = require('tty'); 并替换来修复tty.setRawModeprocess.stdin.setRawMode。仅供参考,但继承解决方案更好。【参考方案3】:

我尝试使用 Node 的 repl 库 - https://nodejs.org/api/repl.html 做类似的事情 - 但没有任何效果。我尝试启动 vscode 和 TextEdit,但在 Mac 上似乎没有办法等待这些程序关闭。将 execSync 与 vim、nano 和 micro 一起使用都会出现异常或挂起终端。

最后,我使用此处给出的示例 https://nodejs.org/api/readline.html#readline_example_tiny_cli 切换到使用 readline 库 - 它使用 micro 工作,例如

import  execSync  from 'child_process'
...
case 'edit':
  const cmd = `micro foo.txt`
  const result = execSync(cmd).toString()
  console.log( result )
  break

它在 Scratch 缓冲区中切换到 micro - 完成后按 ctrl-q,它会在结果中返回缓冲区内容。

【讨论】:

以上是关于如何从 node.js 打开终端应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

node --- 服务一直启动

Node.js:如何将 JS 变量值从脚本传递到终端?

如何从 Node.js 设置终端选项卡标题?

使用 Node.js 打开 23 端口读取数据

如何在超级终端中停止运行 Node.js (Express) 服务器

Node JS:一直在运行服务器[重复]