从Electron应用程序执行bash脚本

li9yvcax  于 2022-12-16  发布在  Electron
关注(0)|答案(1)|浏览(349)

我正在尝试在电子index.html按钮点击中执行bash脚本。下面是我的代码,index.html调用renderer.js,renderer.js打开bash脚本。当我运行代码时,我看到一个可以点击的按钮,但即使我点击它,我也看不到stdout终端的“Hello World”。有人有什么建议可以解决这个问题吗?任何帮助都非常感谢!

索引.html

<h0>My-Flection</h0>
 <button id="openBtn">Open</button>
 <script>
   require('./renderer.js')
 </script>

渲染器.js

const openBtn = document.getElementById('openBtn')
const shell = require('electron').shell

openBtn.addEventListener('click', function(event) {
    shell.openItem("./test.sh")
})

测试.sh

echo "Hello World"
tjvv9vkg

tjvv9vkg1#

Electron的shell方法并不是真的用来运行脚本的,它是用来在系统文件管理器中显示一个文件,“以桌面默认方式”打开一个文件,将文件移到垃圾箱/回收站,以及播放哔哔声等等。
当你使用Electron时,你应该充分利用不同的进程,在主进程中运行你的脚本。这样做可以防止任何可能的渲染进程锁定(如果你愿意,还可以分开你的关注点)。
下面是一个preload.js脚本,它允许通过使用白名单通道名称在主进程和渲染进程之间进行通信。此preload.js脚本中的唯一实现是使用ipcRenderer。有关详细信息,请参阅Context IsolationInter-Process Communication
在这个preload.js脚本中,我们使用通道名称runScript从渲染进程与主进程通信。
preload.js(主进程)

// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;

// White-listed channels.
const ipc = {
    'render': {
        // From render to main.
        'send': [
            'runScript' // Channel name
        ],
        // From main to render.
        'receive': [],
        // From render to main and back again.
        'sendReceive': []
    }
};

// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
    // Allowed 'ipcRenderer' methods.
    'ipcRender', {
        // From render to main.
        send: (channel, args) => {
            let validChannels = ipc.render.send;
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, args);
            }
        },
        // From main to render.
        receive: (channel, listener) => {
            let validChannels = ipc.render.receive;
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender`.
                ipcRenderer.on(channel, (event, ...args) => listener(...args));
            }
        },
        // From render to main and back again.
        invoke: (channel, args) => {
            let validChannels = ipc.render.sendReceive;
            if (validChannels.includes(channel)) {
                return ipcRenderer.invoke(channel, args);
            }
        }
    }
);

preload.js脚本的用法如下...

/**
 * Render --> Main
 * ---------------
 * Render:  window.ipcRender.send('channel', data); // Data is optional.
 * Main:    electronIpcMain.on('channel', (event, data) => { methodName(data); })
 *
 * Main --> Render
 * ---------------
 * Main:    windowName.webContents.send('channel', data); // Data is optional.
 * Render:  window.ipcRender.receive('channel', (data) => { methodName(data); });
 *
 * Render --> Main (Value) --> Render
 * ----------------------------------
 * Render:  window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
 * Main:    electronIpcMain.handle('channel', (event, data) => { return someMethod(data); });
 *
 * Render --> Main (Promise) --> Render
 * ------------------------------------
 * Render:  window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
 * Main:    electronIpcMain.handle('channel', async (event, data) => {
 *              return await promiseName(data)
 *                  .then(() => { return result; })
 *          });
 */

在此main.js脚本中,侦听通道名称runScript上的消息,然后使用spawn运行脚本。
exec可以在需要流式传输的情况下使用。IE:exec缓冲输出。
main.js(主流程)

'use strict';

const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;

const nodePath = require("path");
const nodeChildProcess = require('child_process');

let window;

function createWindow() {
    const window = new electronBrowserWindow({
        x: 0,
        y: 0,
        width: 800,
        height: 600,
        show: false,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            preload: nodePath.join(__dirname, 'preload.js')
        }
    });

    window.loadFile('index.html')
        .then(() => { window.show(); });

    return window;
}

electronApp.on('ready', () => {
    window = createWindow();
});

electronApp.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        electronApp.quit();
    }
});

electronApp.on('activate', () => {
    if (electronBrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

// ---

electronIpcMain.on('runScript', () => {
    // Windows
    let script = nodeChildProcess.spawn('cmd.exe', ['/c', 'test.bat', 'arg1', 'arg2']);

    // MacOS & Linux
    // let script = nodeChildProcess.spawn('bash', ['test.sh', 'arg1', 'arg2']);

    console.log('PID: ' + script.pid);

    script.stdout.on('data', (data) => {
        console.log('stdout: ' + data);
    });

    script.stderr.on('data', (err) => {
        console.log('stderr: ' + err);
    });

    script.on('exit', (code) => {
        console.log('Exit Code: ' + code);
    });
})

一些测试脚本。
test.bat(适用于Windows)

echo "hello World"

echo %1%
echo %2%

test.sh(适用于MacOS和Linux)

echo "Hello World"

echo $1
echo $2

最后,这里是一个简化的index.html文件,在buttonclick上,send通过通道名runScript向主进程发送消息。
index.html(渲染进程)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Electron Test</title>
    </head>

    <body>
        <input type="button" id="button" value="Run Script">
    </body>

    <script>
        document.getElementById('button').addEventListener('click', () => {
            window.ipcRender.send('runScript');
        })
    </script>
</html>

相关问题