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 命令:如何实现交互式操作?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271