Windows 下 Node 执行 exec 返回值乱码的解决方案
Windows 下 Node 执行 exec 返回值乱码的解决方案
本文介绍了在 Windows 下,如何使用 Node 得到正确的 exec 输出编码而无需手动指定输出字符集。
前言
在 node child_process
模块中,允许使用 exec 来开启一个子进程,执行特定的命令,在 Windows 上,如果命令输出有中文, 常常会出现执行命令后的 stdout
为乱码,其原因是简体中文的 Windows 系统默认使用的编码方式是 CP936 ,而 Nodejs 尝试直接使用 UTF8 来进行输出。
其他解决方法
目前网上有很多解决办法,比较常见的有两种,一个是执行命令时提前指定好编码,使用 chcp 65001
使 shell 切换到 UTF8 字符集,但是这个命令并不是同步执行的,很多情况下难以兼容,插入到命令中使用起来也不方便。
第二种方法是做后置处理,在exec 时传递 {encoding:"buffer"}
使用 buffer 输出,然后在回调函数中使用 iconv decode 解码,但是这个方法需要提前知道当前的编码方式。
有一些地方提到可以使用 JsChardet
这个包来做检查,这个包在字符量较大的情况下比较准确,但是还是偶尔会出现准确率不高的情况,主要的 bad case 出现在小部分中文 + 大部分英文的情况,会识别成其他语种。
一种比较易于实现的解决方案
在本例中,我尝试使用 powershell 读取当前 QQ音乐 的标题,用户的设备环境比较单一,基本可以确认是在 windows 下,所以可能使用的字符集编码也比较有限。因此可以通过预先埋设探测字符的方式来做检查。
在命令行中输出一串固定的中文字符,然后遍历常见的中文字符集表,尝试解码,直到某个字符集能够正确解码出目标字符即可。
通过这种方法能够极大的提高检查和识别速度,由于环境也比较单一,因此不可能出现把中文解码为泰语的情况,识别率也能得到保证。
这是上面一种埋值+探测结合的方法,比起 chcp 65001
,只是埋一个字符串对于执行命令来说成本比较低,不需要关注 chcp的执行状态,而相对于 JsChardet
,能够缩小字符集的范围,减小 bad case 的出现几率。
总结
该方案只是对于 乱码问题的解决方法之一,比起上面两个方法,都会显得并不雅观,只是减小了命令编写和识别的难度,而且在执行前后都需要做很多的处理,难以作为一个通用的解决方案来使用。因此该方案仅在开发中文 Windows 下的程序时,可以作为一个相对简单的处理方案。
About
代码开源协议 :MIT
本文使用 powershell 检查 QQ 音乐的代码
exec(`powershell.exe -ExecutionPolicy Bypass "Get-Process QQMusic | Select-Object MainWindowTitle | Format-Wide | Out-String"`)
本文相关代码
import {exec as execute} from 'child_process'
import iconv from 'iconv-lite'
/**
* 使用特定的标记字符串来辅助解码,辨别exec的输出编码
*/
const chineseMark = '$中文标记$'
/**
* @description 执行原生命令
* @param {string} command 命令
* @param {object} options exec 的选项
* @param {number} timeout 超时时间
*/
export function exec (command, options = null, timeout = 300) {
options = {
encoding: 'buffer',
windowsHide: true, // 默认设置为隐藏子窗口
...options
}
let childProcess = null
let exitFlag = false
return Promise.race([
new Promise((resolve, reject) => {
try {
childProcess = execute(command, options, (error, stdout, stderr) => {
if (error) {
reject(error)
} else {
// Decode Buffer
if (Buffer.isBuffer(stdout)) {
let charset = ['cp936', 'utf8'].find((charset) => {
return ~iconv.decode(stdout, charset).indexOf(chineseMark)
})
if (charset) {
[stdout, stderr] = [stdout, stderr].map(v => iconv.decode(v, charset))
stdout = stdout.replace(chineseMark, '') // 移除标记
} else {
[stdout, stderr] = [stdout, stderr].map(v => v.toString('utf8'))
}
// 尝试检查编码
}
resolve(stdout, stderr)
}
})
} catch (e) {
reject(e)
} finally {
exitFlag = true
}
}),
new Promise((resolve, reject) => {
setTimeout(() => {
if (!exitFlag) {
// 子进程未退出,手动结束
childProcess && childProcess.kill()
reject(new Error('EXEC TIMED OUT'))
} else {
resolve()
}
}, Math.max(timeout, 1000))
})
])
}
/**
* @description 获取窗口标题
*/
export function getWindows () {
// return exec('echo hello你好啊' + chineseMark)
return exec(`powershell.exe -ExecutionPolicy Bypass "Get-Process QQMusic | Select-Object MainWindowTitle | Format-Wide | Out-String | &{$input+'${chineseMark}'}"`)
}