6

是否可以创建一个可以从 *nix 命令行严格运行(使用标准输入/标准输出,从终端控制台或通过 ssh 等)运行的单个 Mac OS X 应用程序,但也可以从应用程序图标和使用 Mac GUI 进行所有用户交互(不需要终端)?如果是这样,怎么做?

我已经可以使用两个可执行文件来实现类似的功能,一个是 GUI 前端,另一个是使用管道、套接字、共享内存或 Applescript(等)进行通信。这个问题是我是否可以使用单个可执行文件和单个应用程序进程空间来做到这一点。

有没有办法在不将新的命令行参数传递给命令行可执行文件的情况下做到这一点?(因为存在与使用命令行应用程序相关的遗留问题)。

4

7 回答 7

4

我相信,您正在寻找的是一种确定您是从 GUI 还是命令行启动的方法。一种简单的方法是获取您的父进程标识符。如果您是 GUI 进程,您将由 launchd(用户会话 launchd)启动。如果您是命令行,那么 shell 或脚本已经启动了您(无论如何,不​​是启动)。

现在,如何做到这一点 - 一个简单的方法是使用 proc_info 系统调用 (#336),它由 libproc 方便地包装(检查<libproc.h>。具体来说,您需要<sys/proc_info.h>具有 pbi_ppid 字段的 proc_bsdinfo(请参阅 )来指定您的父级(并且父级的 pbi_comm 会告诉您它的名称 - 以查看它是否已启动)。您可以通过在进入NSApplicationMain.

(与其他答案一样,您的二进制文件最终会出现在您的 中.app/Contents/MacOS/<binary>,所以是的,符号链接是有意义的)

于 2012-08-31T01:16:53.847 回答
3

我不是 100% 确定这是否会起作用,但LSEnvironmentkey in的文档Info.plist似乎表明指定的环境变量只会在您的应用程序由 Finder 启动时定义。

如果您要在LSEnvironment(我们称之为MYAPP_GUI)中定义一个变量并在启动时检查它的值,那么它应该在使用 Finder 时存在,而不是在使用终端时存在。这会告诉你是否显示 GUI 或使用 stdio

于 2012-08-30T19:34:43.487 回答
1

尽管@3doubloons 提供了正确的答案,但我仍然想分享我的代码片段来演示这一点。

首先,LSEnvironment在 Info.plist 中添加一个键。它的值是 env-var-names (作为键)及其字符串(只允许字符串!)值的字典。就我而言,我添加了LaunchUI带有值的键"Yup"

在此处输入图像描述

然后,我的 main() 函数如下所示:

int main(int argc, const char * argv[]) {
    int status = -1;
    @autoreleasepool {
        
        do {
            NSProcessInfo *procInfo = [NSProcessInfo processInfo];

            // deterine "UI" launch versus "command-line
            id uiEnvVar = [[procInfo environment] objectForKey:@"LaunchUI"];
            if([uiEnvVar isKindOfClass:[NSString class]] && [uiEnvVar isEqualToString:@"Yup"] ) {    
                status = NSApplicationMain(argc, argv); //launch app normally with UI
                break;
            }
            
            id arguments = [procInfo arguments];
            if (arguments == nil) {
                printUsage();
                break;
            }
            if ([arguments count] == 1 || [arguments containsObject:@"-h"] || [arguments containsObject:@"-help"]) {
                printUsage();
                status = 0;
                break;
            }
            
            //process further arguments
            if(processArgs(arguments) == NO) {
                printUsage();
                break;
            }

            // kick off protection.

            if(YES != do_work()) {
                break;
            }
            
            status = 0; //

            // if your command-line should stay alive and handle events - you can do the following to block it from exiting.
            [[NSRunLoop currentRunLoop] run];
        } while(false);
    }
    return status;
}

我认为那种覆盖它。享受!

于 2020-11-25T15:05:46.473 回答
0

是的,只需在程序中添加一个开关--command-line--command-line除非在运行时作为参数传递,否则启动普通 GUI 。

./myprog --command-line

这就是它的全部内容。

于 2012-08-30T18:43:46.270 回答
0

这是我在.Net应用程序中所做的,我认为这个想法可以在其他平台上用其他语言实现。

我将创建一个控制台应用程序,然后仍然使用所有 GUI 类。当程序启动时,根据确定它是 GUI 实例而不是命令行实例的任何标准,我要么启动 GUI,要么相应地启动命令行界面。

主要的是,您有一些条件可以确定何时要显示 GUI 而不是命令行。也许通过有关会话的系统变量(是 ssh 会话,然后是命令行,否则是 GUI)。

于 2012-08-30T18:52:06.797 回答
0

我会说这不可能以您期望的方式进行。GUI OSX 应用程序实际上是应用程序包,即它们实际上是除了实际可执行文件之外还包含大量资源的目录。您当然不能在 shell 中执行这些包,就好像它们是命令行可执行文件一样(例如,您不能./MyApplication.app --some-option在 shell 提示符下执行,因为您的 shell 不知道如何执行目录),尽管您可能知道可以运行它们作为 GUI 通过open MyApplication.app. 现在,您可以访问捆绑包中的实际可执行文件,并且有可能获得可执行文件作为 CLI 可执行文件工作 - 在这里直言不讳,因为我对 Xcode 工具及其构建系统没有实际经验,但可能在 CLI 和 GUI 调用中都有实际的可执行文件工作。

尽管如此,这并不是很有用,因为这些可执行文件隐藏在应用程序包中,并且通常无法在 shell 中直接访问(即它们肯定不在您的 $PATH 中,因此您必须像调用它们一样调用它们./MyApplication.app/Contents/MacOS/actualbinaryname)。我在野外看到的常用方法是提供与您的应用程序捆绑在一起的单独 CLI 可执行文件,通常由安装程序或通过单击应用程序中的按钮将其安装在合适的位置(例如 /usr/local/bin)优先。如果您打算这样做,那些 CLI 可执行文件负责与 GUI 应用程序交互(从您的问题中不清楚)。

于 2012-08-30T19:00:39.043 回答
0

抱歉,如果我错过了重点,但如果您使用的是 Objective-C,则不是在 Objective-C 中使用 NSProcessInfo 类并检查“arguments”变量以确定是否已通过任何参数的答案的一部分,并且然后在代码中下定决心下一步该做什么?

于 2012-10-21T02:33:46.280 回答