IT/Node.js

Node.js 쉘 실행하기

루벤초이 2022. 12. 10. 10:35

Node.js 백엔드 서버에서 쉘 스크립트(.sh)나 파이썬 파일(.py)을 실행하는 방법에 대해 알아봅시다. 컴포넌트 뷰는 아래와 같습니다.

Node.js Shell Launcher 구조

 

우선 node.js 프로젝트를 생성하고 필요한 라이브러리를 설치합니다.

 

npm init
npm install child_process mqtt yargs

 

여기서 child_process는 쉘 명령을 실행해주는 라이브러리고

mqtt는 쉘 명령의 결과 즉 콘솔 로그를 서버로 전송하기 위한 MQTT Client입니다.

yargs는 커맨드 라인 입력을 처리하기 위한 라이브러리입니다.

 

이제 메인 코드를 만들어봅시다.

 

const mqtt = require('mqtt');

const logger = (...args) => console.log("[backend] ", ...args);

const SHELL_LAUNCHER = './nodejs/shell-launcher.js'

const DEFAULT_HOST = "ws://127.0.0.1:8888"
const DEFAULT_TOPIC = "backend-shell-launcher"

const topic = DEFAULT_TOPIC;

const mqttClient = mqtt.connect(DEFAULT_HOST);
mqttClient.on('connect', () => {
    logger('mqtt connected');

    mqttClient.subscribe(topic);
    mqttClient.on('message', (topic, message) => logger(`received<<${topic}>> ${message.toString()}`));
});

require('yargs')
    .command({
        command: 'python',
        handler: (argv) => {
            const filename = argv._[1];
            logger(`[Shell Launcher] node backend.js py ${filename}`);
            require(SHELL_LAUNCHER)(true, filename, mqttClient, topic);
        }
    })
    .command({
        command: ['shell', '*'],
        handler: (argv) => {
            const filename = argv._[1];
            logger(`[Shell Launcher] node backend.js shell ${filename}`);
            require(SHELL_LAUNCHER)(false, filename, mqttClient, topic);
        }
    })
    .showHelpOnFail(true)
    .help('help', 'node backend.js pylaunch filepath')
    .demandCommand()
    .argv

 

상단에서 mqtt 클라이언트를 설정하고 연결합니다. DEFAULT_URL에 mqtt broker URL를 입력하면 되겠죠.

토픽에 subscribe한 이유는 SHELL_LAUNCHER publish 데이터가 정상적인지 로컬에서 검증해 보기 위함입니다. 

MQTT가 궁금하다면, https://rubenchoi.tistory.com/entry/MQTT 를 참고하세요.

 

SHELL_LAUNCHER를 좀 더 들여다 봅시다.

 

const TEST_FILE = 'test1'

const logger = (...args) => console.log("[shell-launcher] ", ...args);

class ShellLauncher {
    constructor(isPython = true, filename = TEST_FILE, mqttClient, topic) {
        try {
            const BASE_DIR = require('path').resolve(__dirname, isPython ? "../python" : "../shell");
            const filepath = `${BASE_DIR}/${filename}.${isPython ? 'py' : 'sh'}`;
            logger(`launch ${isPython ? 'python ' : ''} shell for ${filepath}`);
            const command = require('child_process').spawn(isPython ? 'python3' : 'sh', [filepath], { cwd: BASE_DIR });
            command.stdout.on("data", m => {
                const received = m.toString().trim();
                mqttClient.publish(topic, received);
                logger(received);
            });
            command.stderr.on("data", m => {
                logger('********** ERROR **********')
                logger(m.toString())
            });
            command.on("exit", m => logger(`test ends`));
        } catch (err) {
            logger(err);
        }
    }
}

module.exports = (isPython, filename, mqttClient, topic) => {
    return new ShellLauncher(isPython, filename, mqttClient, topic);
};

앞서 backend.js에서 yargs를 통해 커맨드 라인 인자로 python인지 shell인지 받을 수 있는데요,

이를 첫 번째 파라미터 isPython으로 판단하여 python3를 실행할지 sh을 실행할지 결정하게 됩니다.

child_process를 이용하여 파일(두 번째 인자로 받은 파일명)을 실행합니다.

 

문법은 간단합니다.

child_process.spawn(명령, 파라미터들, { cmd : 작업 디렉토리 })로 받은 command 객체를 가지고

command.stdout.on('data', onReceive())으로 처리하는데,

우리 예제에서는 onReceive() 부분을 mqtt publish하는 람다 함수로 넣어줬습니다.

 

이제 검증해봅시다.

우선 node.js child_process를 통해 실행할 쉘 스크립트 test1.sh을 만들어 ./shell 폴더에 넣어둡니다.

#!/bin/bash
x=1
while [ $x -le 5 ]
do
    echo "test counter $x" 
    x=$(( $x + 1 ))
    sleep 1
done

실행합니다.

$ node backend.js shell test1
[backend]  [Shell Launcher] node backend.js shell test1
[shell-launcher]  launch  shell for /tutorial/backend/shell/test1.sh
[shell-launcher]  test counter 1
[backend]  mqtt connected
[shell-launcher]  test counter 2
[backend]  received<<backend-shell-launcher>> test counter 2
[shell-launcher]  test counter 3
[backend]  received<<backend-shell-launcher>> test counter 3
[shell-launcher]  test counter 4
[backend]  received<<backend-shell-launcher>> test counter 4
[shell-launcher]  test counter 5
[backend]  received<<backend-shell-launcher>> test counter 5
[shell-launcher]  test ends

shell-launcher에서 외부 쉘 명령을 수행하고 그 콘솔 로그를 받아 mqtt로 잘 보내는 것을 확인했습니다.

 

728x90
반응형