应用如果需要执行系统的Shell指令,可以通过@ohos.processrunCmd(command: string, options?: ConditionType): ChildProcess函数来执行。

需要说明的是,该方法局限性比较大,通常不建议使用,仅做参考

使用方法

下载Full SDK

由于该api是系统应用权限,且不对普通应用开放,因此需要下载Full SDK,如果已经是Full SDK可以忽略这一步。Full SDK可以在OpenHarmony ci的每日构建上下载对应的11或12的版本,链接:https://ci.openharmony.cn/workbench/cicd/dailybuild/dailylist

img

通过选择OpenHarmony-4.1-Release分支下载api 11 sdk、OpenHarmony-5.0-Release分支下载api 12 sdk。

下载解压后通过DevEco Studio的设置将OpenHarmony SDK指向该目录

关闭selinux

由于selinux的限制,普通应用访问/bin下的可执行文件时,会报权限错误,因此需要关闭selinux。可以通过如下指令关闭:

hdc shell setenforce 0

系统应用权限

由于该api有@systemapi Hide this for inner system use标志,因此需要应用为系统应用。可以通过预置该应用到系统中,或者通过APL的方式获取系统应用权限,方法就不在此赘述,有需要可以搜索相关资料。

相关代码

import process from '@ohos.process';
import buffer from '@ohos.buffer';

async function execCmd(cmd: string): Promise<string> {
  let childProcess: process.ChildProcess = process.runCmd(cmd);

  let outputString: string = '执行失败';
  let errorOutputString: string = '';
  try {
    // 等待子进程结束
    const exitCode = await childProcess.wait();

    // 获取子进程的标准输出
    const output = await childProcess.getOutput();
    outputString = buffer.from(output).toString('UTF-8');

    // 获取子进程的标准错误输出
    if (outputString === '\u0000') {
      const errorOutput = await childProcess.getErrorOutput();
      errorOutputString = buffer.from(errorOutput).toString('UTF-8');
    }

  } catch (error) {
    // 处理可能出现的错误
    Logger.error(TAG, JSON.stringify(error) ?? '');
    throw error as Error;
  }

  if (errorOutputString) {
    throw new Error(errorOutputString);
  }
  return outputString;
}

上述代码定义了execCmd函数,主要流程如下:

  1. 调用runCmd后会返回childProcess对象,该对象表示子进程
  2. 调用childProcess.wait()来等待子进程执行结束
  3. 子进程执行结束后,通过childProcess.getOutput()来获取指令执行后的结果,通过childProcess.getErrorOutput()来获取指令执行后的错误
  4. 如果有错误,则抛出异常
  5. 由于Shell指令会在子进程中执行,因此无需担心阻塞主进程的主线程

execCmd函数使用起来也比较简单,如执行一个简单的ls执行:

execCmd('ls')
    .then(result => {})
    .catch(e => {});

入参可以替换为需要执行的指令

4.1与5.0使用差异

在4.1的系统上,runCmd函数仅需要传入可执行文件的名字即可,如ls、reboot。但是在5.0上,需要传入可执行文件的全路径,如runCmd('/bin/ls')

注意事项

  1. runCmd函数执行后,会fork当前应用进程出一个子进程,并通过childProcess对象表示。待执行的Shell指令会在子进程中执行,这意味着子进程的权限与应用一致,并且没有root权限。通常应用本身无法访问沙箱目录外的文件,那么子进程也同样如此。
  2. 经过测试,runCmd仅支持执行部分/bin下的可执行程序
  3. 如果执行的二进制内部,访问了应用不可访问的目录,会出现不可预料的错误。同理,如果执行了应用没有权限访问的api或操作,也会出现错误
  4. 如果是自己编写的二进制程序需要使用应用来执行,可以考虑将该程序放在应用的assets中,在运行时将该文件拷贝到应用的沙箱目录中,再通过沙箱目录+文件名的方式来执行该程序,可以参考:https://gitee.com/kunyuan-hongke/openharmonyevaluate/blob/master/entry/src/main/ets/test/CpuMapTest.ets

基本原理

runCmd的代码实现位于commonlibrary\ets_utils\js_sys_module\process\native_module_process.cpp下:

    static napi_value ChildProcessConstructor(napi_env env, napi_callback_info info)
    {
        napi_value thisVar = nullptr;
        void* data = nullptr;
        size_t argc = 2; // 2:The number of parameters is 2
        napi_value args[2] = { nullptr }; // 2:The number of parameters is 2
        NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisVar, &data));

        DealType(env, args, argc);
        auto objectInfo = new ChildProcess();

        objectInfo->InitOptionsInfo(env, args[1]);

        objectInfo->Spawn(env, args[0]);

        NAPI_CALL(env, napi_wrap(
            env, thisVar, objectInfo,
            [](napi_env env, void* data, void* hint) {
                auto objectResult = reinterpret_cast<ChildProcess*>(data);
                if (objectResult != nullptr) {
                    delete objectResult;
                    objectResult = nullptr;
                }
            },
            nullptr, nullptr));

        return thisVar;
    }

实际上就是实例化了一个ChildProcess对象,并调用了其Spawn函数:

    void ChildProcess::Spawn(napi_env env, napi_value command)
    {
        int ret = pipe(stdOutFd_);
        if (ret < 0) {
            HILOG_ERROR("ChildProcess:: pipe1 failed %{public}d", errno);
            return;
        }
        ret = pipe(stdErrFd_);
        if (ret < 0) {
            HILOG_ERROR("ChildProcess:: pipe2 failed %{public}d", errno);
            return;
        }
        std::string strCommnd = RequireStrValue(env, command);
        pid_t pid = fork();
        if (!pid) {
            close(stdErrFd_[0]);
            close(stdOutFd_[0]);
            dup2(stdOutFd_[1], 1);
            dup2(stdErrFd_[1], 2); // 2:The value of parameter
            if (execl("/bin/sh", "sh", "-c", strCommnd.c_str(), nullptr) == -1) {
                HILOG_ERROR("ChildProcess:: execl command failed");
                _exit(127); // 127:The parameter value
            }
        } else if (pid > 0) {
            if (optionsInfo_ == nullptr) {
                HILOG_ERROR("ChildProcess:: optionsInfo_ is nullptr");
                return;
            }
            optionsInfo_->pid = pid;
            ppid_ = getpid();
            CreateWorker(env);
            napi_value resourceName = nullptr;
            napi_create_string_utf8(env, "TimeoutListener", strlen("TimeoutListener"), &resourceName);
            napi_create_async_work(
                env, nullptr, resourceName, TimeoutListener,
                [](napi_env env, napi_status status, void* data) {
                    OptionsInfo* optionsInfo = reinterpret_cast<OptionsInfo*>(data);
                    napi_delete_async_work(env, optionsInfo->worker);
                    delete optionsInfo;
                    optionsInfo = nullptr;
                },
                reinterpret_cast<void*>(optionsInfo_), &optionsInfo_->worker);
            napi_queue_async_work_with_qos(env, optionsInfo_->worker, napi_qos_user_initiated);
            close(stdErrFd_[1]);
            close(stdOutFd_[1]);
        } else {
            HILOG_ERROR("ChildProcess:: child process create failed");
        }
    }

Spawn函数中,就是经典的fork函数的写法:

  1. 先通过pipe函数创建两个管道,子进程可以通过该管道写入结果,父进程则通过管道读出结果。
  2. 获取待执行的指令
  3. 通过fork创建子进程,创建的子进程基本与父进程完全相同,两个进程都是从fork函数之下的代码开始执行,区别则是pid不同
    1. 如果pid等于0,则意味着这段代码在子进程中执行
    2. 如果pid大于0,则意味着这段代码在父进程中执行
    3. 如果pid小于0,则意味着这段代码在父进程中执行,且创建子进程出现错误
  4. 在子进程中,首先通过dup2函数将之前创建的两个管道,替换为标准输出与错误输出,以便将结果传递给父进程。然后通过execl函数对子进程进行重载,来执行一个可执行文件。即执行/bin/sh -c 待执行的指令,这样就达到了执行指令的目的
  5. 在主进程中,主要的流程是创建任务用于接受子进程的输出,以及超时相关的回调。
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐