是否可以在不需要外部数据库的情况下为CLI应用程序实施共享状态管理?

发布于 2025-02-06 20:08:08 字数 1596 浏览 1 评论 0原文

我想创建一个CLI应用程序,我认为这个问题不是关于特定技术的,而是为了复制目的,我将Node与命令line-commands (但我知道还有很多其他,例如=“ nofollow noreferrer”>指挥官)。

给定以下示例代码,

#!/usr/bin/env node

'use strict';

const commandLineArgs = require('command-line-args');
const commandLineCommands = require('command-line-commands');
const commandLineUsage = require('command-line-usage');

let isRunning = false; // global state

let commandResult;

try {
    commandResult = commandLineCommands([ 'start', 'info', 'help' ]);
} catch (error) {
    console.error('Invalid command.');
    process.exit(1);
}

if (commandResult.command === null || commandResult.command === 'help') {
    const commandInfo = commandLineUsage([
        { header: 'start', content: 'Sets the value to true' },
        { header: 'info', content: 'Gets the current value' },
    ]);

    console.log(commandInfo);
    process.exit(0);
}

let options;

try {
    options = commandLineArgs([], { argv: commandResult.argv });
} catch (error) {
    console.error('Invalid argument.');
    process.exit(1);
}

if (commandResult.command === 'start') {
    isRunning = true;
} else if (commandResult.command === 'info') {
    console.info({ isRunning });
}

布尔值isrunning表示共享状态。调用开始命令将其值设置为true。但是调用info命令显然启动了一个新的进程,并以其初始虚假值打印了新的变量iSrunning

保持这种状态的最喜欢的技术是什么? CLI是否必须使用外部数据库(例如本地文件系统),或者是否有一些方法可以将信息保存在存储器中直至关闭?

在系统上生成我自己的文件并将此变量存储到它对我来说就像是过度杀伤。

I want to create a CLI application and I think this question is not about a specific technology but for the sake of reproduction purposes I'm using Node with command-line-commands ( but I know there are plenty others, e.g. commander ).

Given the following sample code

#!/usr/bin/env node

'use strict';

const commandLineArgs = require('command-line-args');
const commandLineCommands = require('command-line-commands');
const commandLineUsage = require('command-line-usage');

let isRunning = false; // global state

let commandResult;

try {
    commandResult = commandLineCommands([ 'start', 'info', 'help' ]);
} catch (error) {
    console.error('Invalid command.');
    process.exit(1);
}

if (commandResult.command === null || commandResult.command === 'help') {
    const commandInfo = commandLineUsage([
        { header: 'start', content: 'Sets the value to true' },
        { header: 'info', content: 'Gets the current value' },
    ]);

    console.log(commandInfo);
    process.exit(0);
}

let options;

try {
    options = commandLineArgs([], { argv: commandResult.argv });
} catch (error) {
    console.error('Invalid argument.');
    process.exit(1);
}

if (commandResult.command === 'start') {
    isRunning = true;
} else if (commandResult.command === 'info') {
    console.info({ isRunning });
}

The boolean isRunning indicates a shared state. Calling the start command sets its value to true. But calling the info command obviously starts a new process and prints a new variable isRunning with its initial falsy value.

What is the prefered technology to keep such state? Must the CLI use an external database ( e.g. local filesystem) or are there some ways to keep the information in memory until shutdown?

Generating my own file on the system and storing this variable to it feels like an overkill to me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

物价感观 2025-02-13 20:08:08

旧的跨平台黑客攻击是打开已知的TCP端口。能够打开端口的第一个过程将获得端口。试图打开端口的所有其他过程都将获得 eaddrinuse 错误:

const net = require('net');

const s = net.createServer();

s.on('error',() => {
    console.log('Program is already running!');

    // handle what to do here
});

s.listen(5123,'127.0.0.1',() => {
    console.log('OK');

    // run your main function here
});

这在任何OS上使用任何语言都起作用。您只需要注意一件事 - 使用您正在使用的端口,可能会意外地使用其他程序。

我最初在tcl wiki上遇到了这种技术: https:///wiki.tcl-lang .org/page/singleton+应用程序

An old cross-platform hack is to open a known TCP port. The first process able to open the port will get the port. All other processes trying to open the port will get an EADDRINUSE error:

const net = require('net');

const s = net.createServer();

s.on('error',() => {
    console.log('Program is already running!');

    // handle what to do here
});

s.listen(5123,'127.0.0.1',() => {
    console.log('OK');

    // run your main function here
});

This works in any language on any OS. There is only one thing you need to be careful of - some other program may be accidentally using the port you are using.

I originally came across this technique on the Tcl wiki: https://wiki.tcl-lang.org/page/singleton+application.

兔姬 2025-02-13 20:08:08

另一个旧的黑客是尝试创建一个符号链接。

创建符号链接通常是由大多数Unix和Unix型OS所保证的。因此,使用此技术的潜在种族条件没有问题(与创建常规文件不同)。我认为它也是Windows上的原子(根据Posix规格),但我不确定:

const fs = require('fs');

const scriptName = process.argv[1];
const lockFile = '/tmp/my-program.lock';

try {
    fs.symlinkSync(lockFile, scriptName);

    // run your main function here

    fs.unlinkSync(lockFile);
}
catch (err) {
    console.log('Program already running');

    // handle what to do here
}

注意:创建符号链接是原子链接,但在符号链接上的其他操作不能保证是原子质。特别要谨慎地假设更新符号是原子 - 它不是。更新Symlinks涉及两个操作:删除链接然后创建链接。在您的过程创建一个符号链接后,可以执行第二个进程的删除操作,从而导致两个进程认为它们是唯一运行的过程。在上面的示例中,我们删除链接 创建它,而不是之前。

Another old hack for this is to try and create a symlink.

Creating symlinks are generally guaranteed to be atomic by most Unix and Unix-like OSes. Therefore there is no issue with potential race conditions using this technique (unlike creating a regular file). I presume it is also atomic on Windows (as per POSIX spec) but I'm not entirely sure:

const fs = require('fs');

const scriptName = process.argv[1];
const lockFile = '/tmp/my-program.lock';

try {
    fs.symlinkSync(lockFile, scriptName);

    // run your main function here

    fs.unlinkSync(lockFile);
}
catch (err) {
    console.log('Program already running');

    // handle what to do here
}

Note: While creating symlinks are atomic, other operations on symlinks are not guaranteed to be atomic. Specifically be very careful of assuming that updating a symlink is atomic - it is NOT. Updating symlinks involve two operations: deleting the link and then creating the link. A second process may execute its delete operation after your process creates a symlink causing two processes to think that they're the only ones running. In the example above we delete the link after creating it, not before.

千鲤 2025-02-13 20:08:08

一种方法是使用本地Web服务器。

index.js

const commandLineArgs = require('command-line-args');
const commandLineCommands = require('command-line-commands');
const commandLineUsage = require('command-line-usage');
var http = require('http');

let globalState = {
    isRunning: false
}

let commandResult;

try {
    commandResult = commandLineCommands([ 'start', 'info', 'help' ]);
} catch (error) {
    console.error('Invalid command.');
    process.exit(1);
}

if (commandResult.command === null || commandResult.command === 'help') {
    const commandInfo = commandLineUsage([
        { header: 'start', content: 'Sets the value to true' },
        { header: 'info', content: 'Gets the current value' },
    ]);

    console.log(commandInfo);
    process.exit(0);
}

let options;

try {
    options = commandLineArgs([], { argv: commandResult.argv });
} catch (error) {
    console.error('Invalid argument.');
    process.exit(1);
}

if (commandResult.command === 'start') {
    globalState.isRunning = true;
    http.createServer(function (req, res) {
        res.write(JSON.stringify(globalState));
        res.end();
    }).listen(9615);
} else if (commandResult.command === 'info') {
    console.info({ globalState });
}

index2.js

var http = require('http');

var req = http.request({ host: "localhost", port: 9615, path: "/" }, (response) => {
    var responseData = "";

    response.on("data", (chunk) => {
        responseData += chunk;
    });

    response.on("end", () => {
        console.log(JSON.parse(responseData));
    });
});

req.end();
req.on("error", (e) => {
    console.error(e);
});

这里index.js是一个持有“共享 /全局状态”并创建与之通信的Web服务器的程序。其他程序(例如Index2.js)可以提出HTTP请求并要求全球状态。您也可以让其他程序通过索引来改变状态。

这不必像这样的http来完成,您还可以使用 node-rpc “ nofollow noreferrer”> node-ipc 。我认为最简单的工作示例是使用本地的HTTP客户端和服务器进行操作。

无论哪种方式,我都认为您要寻找的单词是 nofollow noreferrer“> inter Process communication(ipc) a>或远程过程调用(rpc)。我不明白为什么一个人也无法使用 websockets 。即使您可以实施某种类型的亲子过程交流,儿童过程也可能无法在这里工作,因为只有主要过程产生的子进程才能使用。

编辑

更仔细地阅读您的问题后,我认为这只是“保持”启动命令后“保持”“控制台会话”并设置Isrunning变量的问题。

查看此内容:

const commandLineArgs = require('command-line-args');
const commandLineCommands = require('command-line-commands');
const commandLineUsage = require('command-line-usage');
const prompt = require('prompt-sync')();

let globalState = {
    isRunning: false
}

let commandResult;

try {
    commandResult = commandLineCommands([ 'start', 'info', 'help' ]);
} catch (error) {
    console.error('Invalid command.');
    process.exit(1);
}

if (commandResult.command === null || commandResult.command === 'help') {
    const commandInfo = commandLineUsage([
        { header: 'start', content: 'Sets the value to true' },
        { header: 'info', content: 'Gets the current value' },
    ]);

    console.log(commandInfo);
    process.exit(0);
}

let options;

try {
    options = commandLineArgs([], { argv: commandResult.argv });
} catch (error) {
    console.error('Invalid argument.');
    process.exit(1);
}

if (commandResult.command === 'start') {
    globalState.isRunning = true;

    while(globalState.isRunning)
    {
        let cmd = prompt(">");

        if(cmd === "exit")
            process.exit(0);
        if(cmd === "info")
            console.info({ globalState });
    }
   
} else if (commandResult.command === 'info') {
    console.info({ globalState });
}

在这里,我在循环中使用strips-sync库时,当程序与start> start命令调用时。无限期保留“控制台会话”,直到用户键入退出。我还添加了示例,以防用户类型info

示例:

“

One way would be to use a local web server.

index.js

const commandLineArgs = require('command-line-args');
const commandLineCommands = require('command-line-commands');
const commandLineUsage = require('command-line-usage');
var http = require('http');

let globalState = {
    isRunning: false
}

let commandResult;

try {
    commandResult = commandLineCommands([ 'start', 'info', 'help' ]);
} catch (error) {
    console.error('Invalid command.');
    process.exit(1);
}

if (commandResult.command === null || commandResult.command === 'help') {
    const commandInfo = commandLineUsage([
        { header: 'start', content: 'Sets the value to true' },
        { header: 'info', content: 'Gets the current value' },
    ]);

    console.log(commandInfo);
    process.exit(0);
}

let options;

try {
    options = commandLineArgs([], { argv: commandResult.argv });
} catch (error) {
    console.error('Invalid argument.');
    process.exit(1);
}

if (commandResult.command === 'start') {
    globalState.isRunning = true;
    http.createServer(function (req, res) {
        res.write(JSON.stringify(globalState));
        res.end();
    }).listen(9615);
} else if (commandResult.command === 'info') {
    console.info({ globalState });
}

index2.js

var http = require('http');

var req = http.request({ host: "localhost", port: 9615, path: "/" }, (response) => {
    var responseData = "";

    response.on("data", (chunk) => {
        responseData += chunk;
    });

    response.on("end", () => {
        console.log(JSON.parse(responseData));
    });
});

req.end();
req.on("error", (e) => {
    console.error(e);
});

Here the index.js is a program that holds the "shared / global state" as well as creates a web server to communicate with. Other programs such as index2.js here can make a http request and ask for the global state. You could also let other programs change the state by having index.js listen to some specific request and act accordingly.

This doesn't have to be done with http like this, you could also use something like node-rpc or node-ipc. I thought the easiest working example would be to do it with a local http client and server.

Either way, I think the word for what you are looking for is Inter Process Communication (IPC) or Remote Procedure Call (RPC). I don't see why one couldn't also utilize websockets as well. Child processes probably won't work here, even if you could implement some kind of parent-child process communication, because only the child processes spawned by the main process could use that.

EDIT

After reading your question more carefully, I think that this is just a matter of "keeping" the "console session" after start command and setting the isRunning variable.

Check this out:

const commandLineArgs = require('command-line-args');
const commandLineCommands = require('command-line-commands');
const commandLineUsage = require('command-line-usage');
const prompt = require('prompt-sync')();

let globalState = {
    isRunning: false
}

let commandResult;

try {
    commandResult = commandLineCommands([ 'start', 'info', 'help' ]);
} catch (error) {
    console.error('Invalid command.');
    process.exit(1);
}

if (commandResult.command === null || commandResult.command === 'help') {
    const commandInfo = commandLineUsage([
        { header: 'start', content: 'Sets the value to true' },
        { header: 'info', content: 'Gets the current value' },
    ]);

    console.log(commandInfo);
    process.exit(0);
}

let options;

try {
    options = commandLineArgs([], { argv: commandResult.argv });
} catch (error) {
    console.error('Invalid argument.');
    process.exit(1);
}

if (commandResult.command === 'start') {
    globalState.isRunning = true;

    while(globalState.isRunning)
    {
        let cmd = prompt(">");

        if(cmd === "exit")
            process.exit(0);
        if(cmd === "info")
            console.info({ globalState });
    }
   
} else if (commandResult.command === 'info') {
    console.info({ globalState });
}

Here I am using prompt-sync library inside a loop when the program is called with a start command. The "console session" is kept indefinitely until the user types exit. I also added and example for in case the user types info.

Example:

example output

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文