Dart 执行 shell 命令:如何在 macOS 平台提高权限?

上一篇:无
下一篇:Dart 执行 shell 命令:如何实现交互式操作?

  1. macOS 系统本地如何实现?
    使用 macOS 自带的“脚本编辑器”应用,新建脚本:
    run_shell.scpt:

    # 此处设置的命令将使用 root 权限执行
    set sudo_script to "
    cd ~/
    touch test-sudo
    "
    do shell script sudo_script with prompt "应用名称:设置环境变量……" with administrator privileges
    
    # 此处设置的命令将使用当前用户权限执行
    set normal_script to "
    cd ~/
    touch test-normal
    "
    do shell script normal_script
    

    打开终端,执行如下命令:

    osascript run_shell.scpt的路径
    
  2. Dart 中如何实现?
    如果使用脚本编辑器,在终端输入以下命令也可以执行:

    osascript -e 'do shell script "cd ~/;touch test-sudo" with prompt "应用名称:设置环境变量……" with administrator privileges'
    

    但是,如果将上述命令直接写到 dart 中,执行并不生效,怎么处理?继续往下看。

  3. Dart 代码实现步骤
    a)将 shell 命令保存为一个 .sh 文件,添加可执行权限。
    b)将 osascript 命令保存到另一个 .sh 文件,添加可执行权限。
    c)在 dart 代码中使用 Process 运行 shell 命令,去执行 shell_osascript.sh 脚本文件。
    此时,系统会弹出权限申请窗口,输入密码就可以执行了。
    在 dart 代码中封装一个方法:runByAdmin(String command); 思路如下:
    command 参数是需要执行的 shell 命令集合,可以是多条 shell 语句。

  4. Dart 实现示例代码
    main.dart:

    import 'dart:convert';
    import 'dart:io';
    
    import 'package:jdflutter_base/jdf_process_input.dart';
    
    import 'jdf_logger.dart';
    
    class JDFProcess {
      static const String _tag = 'JDFProcess';
      static JDFLogger _logger = JDFLogger.getLogger('JDFProcess');
      static String _executable = 'bash';
    
      static String _user = Platform.environment['LOGNAME'];
      static String get user => _user == '' ? null : _user;
    
      static String _user_home = Platform.environment['HOME'];
      static String get user_home => _user_home == '' ? null : _user_home;
    
      static void initUserValues() {
        if (_user == null) {
          ProcessResult result = JDFProcess.runSync('echo \$USER');
          final String tmp = result?.stdout?.trim()?.replaceAll('\n', '') ?? '';
          _user = tmp;
        }
        if (_user_home == null) {
          String tmp = user;
          if (tmp != null && tmp != '') {
            _user_home = '/Users/$tmp';
          }
        }
      }
    
      static String _makeCommand(String command) {
        return command;
      }
    
      static String _getExecutable() {
        if (Platform.environment != null &&
            Platform.environment.containsKey('SHELL')) {
          String shell = Platform.environment['SHELL'];
          if (null != shell) {
            if (shell.contains('/')) {
              shell = shell.split('/').last;
              if (shell != null && shell != '') {
                _executable = shell;
              }
            }
          }
        }
        _logger.logd('#! ' + _executable);
        return _executable;
      }
    
      static Map<String, String> _getEnviroment(Map<String, String> environment) {
        if (environment == null) {
          environment = Platform.environment;
        }
        return environment;
      }
    
      static Future<Process> start(
        String command, {
        String workingDirectory,
        List<String> inputFlags,
        void onStart(int pid),
        void onData(String data),
        void onError(String error),
        String onInput(String error, String flag),
        void onExit(int code),
        Map<String, String> environment,
        bool includeParentEnvironment: true,
        bool runInShell: false,
        ProcessStartMode mode: ProcessStartMode.normal,
        bool withInputListen = false,
      }) async {
        _executable = _getExecutable();
    
        environment = _getEnviroment(environment);
        _logger.logd(environment);
    
        Process process = await Process.start(
          _executable,
          [
            '-l',
            '-c',
            _makeCommand(command),
          ],
          workingDirectory: workingDirectory,
          environment: environment,
          includeParentEnvironment: includeParentEnvironment,
          mode: mode,
        );
    
        JDFProcessInput _processInput;
        if (withInputListen) {
          _processInput = JDFProcessInput(process);
          _processInput.run();
        }
    
        if (null != onStart) {
          onStart(process.pid);
        }
    
        process.stdout.transform(utf8.decoder).listen((data) {
          data = data?.trim();
          if (data.replaceAll('\n', '') == '') {
            data = '';
          }
    
          if (null != onData) {
            onData(data);
          }
    
          _processInputFlags(
            process: process,
            data: data,
            onInput: onInput,
            inputFlags: inputFlags,
          );
        });
    
        process.stderr.transform(utf8.decoder).listen((error) {
          error = error?.trim();
          if (error.replaceAll('\n', '') == '') {
            error = '';
          }
    
          if (null != onError) {
            onError(error);
          }
    
          _processInputFlags(
            process: process,
            data: error,
            onInput: onInput,
            inputFlags: inputFlags,
          );
        });
    
        process.exitCode.then((value) {
          if (_processInput != null) {
            _processInput.finish();
            _processInput = null;
          }
          process = null;
          if (null != onExit) {
            onExit(value);
          }
        });
    
        return process;
      }
    
      static void _processInputFlags({
        Process process,
        String data,
        List<String> inputFlags,
        String onInput(String data, String flags),
      }) {
        String flag;
        List<String> dataList = data.split('\n');
        for (int i = 0; i < (inputFlags?.length ?? 0); i++) {
          String element = inputFlags[i];
          for (int n = 0; n < dataList.length; n++) {
            String temp = dataList[n];
            if (temp.endsWith(element) || temp.startsWith(element)) {
              flag = element;
              break;
            }
          }
          if (null != flag) {
            break;
          }
        }
    
        if (null != flag) {
          _logger.logd('---> onInput() ' + data, tag: _tag);
          if (null != onInput) {
            String input = onInput(data, flag);
            process.stdin.writeln(input);
          }
        }
      }
    
      static Future<ProcessResult> run(
        String command, {
        String workingDirectory,
        Map<String, String> environment,
        bool includeParentEnvironment: true,
        bool runInShell: false,
        Encoding stdoutEncoding: systemEncoding,
        Encoding stderrEncoding: systemEncoding,
      }) async {
        _executable = _getExecutable();
        _logger.logd(_executable);
        environment = _getEnviroment(environment);
        _logger.logd(environment);
        ProcessResult result = await Process.run(
          _executable,
          [
            '-l',
            '-c',
            _makeCommand(command),
          ],
          workingDirectory: workingDirectory,
          environment: environment,
          includeParentEnvironment: includeParentEnvironment,
          runInShell: runInShell,
          stdoutEncoding: stdoutEncoding,
          stderrEncoding: stderrEncoding,
        );
        return result;
      }
    
      static ProcessResult runSync(
        String command, {
        String workingDirectory,
        Map<String, String> environment,
        bool includeParentEnvironment: true,
        bool runInShell: false,
        Encoding stdoutEncoding: systemEncoding,
        Encoding stderrEncoding: systemEncoding,
      }) {
        _executable = _getExecutable();
        _logger.logd(_executable);
        environment = _getEnviroment(environment);
        _logger.logd(environment);
        ProcessResult result = Process.runSync(
          _executable,
          [
            '-l',
            '-c',
            _makeCommand(command),
          ],
          workingDirectory: workingDirectory,
          environment: environment,
          includeParentEnvironment: includeParentEnvironment,
          runInShell: runInShell,
          stdoutEncoding: stdoutEncoding,
          stderrEncoding: stderrEncoding,
        );
        return result;
      }
    }
    
  5. Dart 示例代码执行效果

    run_by_admin.png

    运行程序弹出系统弹窗,输入密码执行。执行完成后自动删除创建的两个 .sh 文件。
    上面代码中是在 Home 目录创建一个 test_file 文件,5s 后删除,运行结果如下:

    $ cd ~/
    $ ls -la test_file
    -rw-r--r--  1 root  staff  0  8 27 01:20 test_file
    

    可以看到,创建的文件所属默认为 root 用户,若有需要,可以在 shell 命令中将创建的文件所属修改为当前用户,修改 shell 命令:

    void main(List<String> args) {
      final String user_home = Platform.environment['HOME'];
      RootProcessForMacOS.run(
        '创建一个测试文件……',
        '''
    cd ~/
    touch test_file
    chown \$user_home
    sleep 5
    ''',
      ).whenComplete(() {
        RootProcessForMacOS.run(
          '删除刚才创建的文件...',
          'cd ~/;rm -rf test_file;',
        );
      });
    }
    
  6. FAQ

    1. 多个程序同时使用这种方式,如何避免 .sh 文件被覆盖?
      答:可以通过 uuid 为 .sh 文件设置唯一名称。
    2. 使用 Dart 执行 shell 命令时,如何进行与执行过程进行交互?
      答:可以参考 [Dart 执行 shell 命令:如何实现交互式操作?

上一篇:无
下一篇:Dart 执行 shell 命令:如何实现交互式操作?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。