从命令行轻松启动Mac OS应用


22

我在命令行中做了很多工作,并且发现自己定义了许多形式的别名:

alias skim='/Applications/Skim.app/Contents/MacOS/Skim'

有没有一种方法可以添加魔法,例如,要求可执行文件“ foo”自动使用可执行文件/Applications/Foo.app/Contents/MacOS/Foo?这是在OS X 10.6上。

(我知道我可以做到open foo.pdf,但是Skim不是默认的PDF阅读器,我想要一个通用的解决方案-在某些情况下,将有问题的应用程序设置为文件的默认处理程序是不合适的。)


22
你可以open -a Skim foo.pdf

1
@mankoff:很好,我忘了您可以使用-a告诉Opens您想要从Applications目录中获取一个应用程序。您应该将其更改为答案:)
Robert S Ciaccio 2010年

@Reid:您可以编辑您的问题以包括您希望能够键入的示例命令行吗?
罗伯特·西亚乔

@mankoff,这表示他需要别名来确保或扩展制表符的完成
Robert S Ciaccio

@Reid解决方案有问题吗?

Answers:


10

终于我明白了:将此添加到您的 .bash_profile

function foomagick() {
    rm -f ~/.foomagick.tmp
    ls /Applications/ | grep "\.app" | grep -v iWork | while read APP; do
        # clean it up                                                           
        a=`echo $APP | sed s/\ //g`;
        a=`echo $a | sed s/\'//g`;
        echo alias ${a%.*}="'open -a \"${APP%.*}\"'" >> ~/.foomagick.tmp
    done
    source ~/.foomagick.tmp
    rm ~/.foomagick.tmp  
}
foomagick()

现在进行以下工作:

Skim # open Skim.app
Skim foo.pdf
FireFox http://google.com
FireFox google.com # ERROR. Looks for local file.

瑞德编辑:

我将以上内容实现为Python语言脚本,该脚本使包装脚本代替了别名。您将需要走~/bin/mankoffmagic自己的路。如果您希望包装程序自动更新,请定期从cron或类似工具运行它。

#!/usr/bin/python
#
# This script automagically updates a set of wrapper shell scripts in
# ~/bin/mankoffmagic which call Mac apps installed in /Applications.
#
# Inspired by mankoff's shell alias posted on apple.stackexchange.com; see:
# http://apple.stackexchange.com/questions/4240/concisely-starting-mac-os-apps-from-the-command-line/4257#4257
#
# Notes/Bugs:
#
# 1. Does not follow symlinks (aliases?)
#
# 2. Assumes that application names do not contain double-quotes.
#
# 3. Not very smart about finding the actual binary (it guesses). This is
# wrong sometimes, e.g. Firefox. Probably a deeper understanding of the app
# package structure would fix this.

import copy
import glob
import os
import os.path
import re

BINDIR = os.path.expandvars("$HOME/bin/mankoffmagic")
APP_RE = re.compile(r'(.*)\.app$')
STRIP_RE = re.compile(r'[\W_]+')

def main():
   # We aggressively delete everything already in BINDIR, to save the trouble
   # of analyzing what should stay
   for f in glob.glob("%s/*" % BINDIR):
      os.unlink(f)

   # Walk /Applications and create a wrapper shell script for each .app dir
   for (root, dirs, files) in os.walk("/Applications"):
      dirs_real = copy.copy(dirs)  # so we can manipulate dirs while looping
      for d in dirs_real:
         #print "checking %s" % os.path.join(root, d)
         m = APP_RE.search(d)
         if (m is not None):
            #print "Found " + m.group()
            dirs.remove(d)  # no need to recurse into app
            create_script(root, d, m.group(1))

def create_script(path, appdir, appname):
   # remove non-alphanumerics and downcase it
   wrapper = STRIP_RE.sub('', appname).lower()
   wrapper = os.path.join(BINDIR, wrapper)
   fp = open(wrapper, "w")
   # Twiddle the comments in the script depending on whether you want to
   # invoke the binary or use "open" -- the former lets you use any
   # command-line args, while the latter is more Mac-like (app puts itself in
   # the front, etc.)
   fp.write("""
#!/bin/sh
exec "%s/%s/Contents/MacOS/%s" "$@"
#open -a "%s" "$@"
""" % (path, appdir, appname, appname))
   fp.close()
   os.chmod(wrapper, 0700)


if (__name__ == "__main__"):
   main()

出现“ grep -v iWork”只是因为“ iWork '09”中的撇号搞砸了。请注意,如果您将应用程序存储在其他地方(〜/ local / Applications),只需将其添加到ls命令中,该命令也将在此处运行。

好东西。我喜欢它是动态的,因此您不会遇到任何维护问题,例如清理旧别名。
罗伯特·西亚乔

谢谢@mankoff!闻起来很棒-一旦我对其进行测试并且可以正常工作,我将标记为接受答案。非常感激。
里德2010年

好,这有效。在应用程序上确实带有撇号(并且名称中可能还有其他有趣的字符)时,它确实会中断。我研究了您的解决方案,并在Python中做了类似的事情,我将在另一个答案中发布-尽管如果这不是正确的礼节,我很乐意将其添加到您的答案中(或其他内容)。
里德

8

您不需要任何花哨的东西,您已经有了答案。尝试:

open /Applications/Foo.app bar.pdf

编辑时

根据下面的评论,我认为我的答案仍然会相对相似...我将使一个函数覆盖open,执行push到/Applications,调用/usr/bin/open $appName.app $args,执行popd并返回。

我很喜欢shell脚本,但是下面的内容涵盖了一些特殊情况,使您可以使用与apple一样的开放语法。我喜欢保持环境尽可能清洁。

我确定语法来自外层空间:

function open($appName, $args)
{
    #if we are calling open without a path like /Applications/TextEdit.app AND
    #    $appName ends in '.app'
    if (!hasParent($appName) && isApp($appName))
    {
        pushd /Applications
        /usr/bin/open ${appName}.app $args &
        popd
        return
    }
    #otherwise, use open as normal
    /usr/bin/open $appName $args
    return
}

在第二次编辑

看@mankoff对原始问题的评论,上述功能中的大多数内容可能会浪费时间,因为您只能使用open -a $appName。因此,mankoff可能是最简单的解决方案,应将其注释更改为答案;)


我认为@Reid想要一种更短的方法,无需手动创建所有别名。

但是,如果他在.app软件包中使用“ open”,则不必为unix可执行文件创建别名。我给他的印象是他在创建别名,因为他认为直接从命令行启动应用程序的唯一方法是一直进入MacOS目录并执行应用程序中“真实”可执行文件。因此,在这种情况下,别名至少可以节省键入启动应用程序所需的额外三级目录结构。也许我误会了他的问题。
罗伯特·西亚乔

@calevara,谢谢-@mankoff是正确的;这是关于长度。
里德2010年

3
这有点误用了编辑功能。如果您以后想提出第二个答案,请执行此操作。不要编辑原始答案,不要将其收到的票数背离您的新想法。
杰夫·斯文森

1
我不同意...答案仍然非常相似,因为他将使用相同的语法。不仅如此,如果投票者不喜欢新的答案,他们可以更改投票,因为答案已经过编辑。这就是为什么您可以在编辑后更改投票的原因。这也是为什么我将“编辑”以粗体显示的原因。
罗伯特·西亚乔

4

我可以想到两种解决方案:

更简单的方法 -使用Info.plist每个.app Contents目录中的文件,为键CFBundleExecutable创建值的索引。然后添加一个简短的别名,该别名使用可执行文件的名称和您要传递的参数来调用脚本(perl,python等)。

您的索引将是成对的可执行文件名称和这些可执行文件的路径。您必须编写一个定期计划脚本来保持此更新。

您最终可以致电:

f foo file.txt

其中f是脚本的别名,该脚本检查索引以查找名为的可执行文件foo

总而言之,获得所需功能的工作量并不大。

困难的方法 -扩展外壳程序以补充其command_not_found错误的处理。本质上,您将method_missing在所使用的任何shell中实现Ruby样式的功能。当command_not_found被抛出,你的方法会检查你安装的应用程序可执行文件的名称。


2

Applescript进行救援:

osascript -e 'tell application "iTunes"' -e "activate" -e 'end tell'

将应用程序的名称替换为您要启动的应用程序,然后完成。当然,您可以根据需要将其设置为shell函数:

function f() {
    osascript <<EOF
tell application "$1"
  activate
end tell
EOF
}

并以这种方式使用:

f iTunes

我讨厌applescript,但有时它很有用,而且我相信唯一在命令行上通过名称来寻址应用程序的唯一方法。其他所有内容都需要完整的路径。


不支持制表符补全。需要准确记住应用名称。如果外壳脚本可以完成所有操作,则看不到为什么需要AppleScript(尤其是您讨厌)。

1
我从未意识到open -a可以在没有文件名作为参数的情况下使用。自从NextStep以来,我一直在使用开放,有时是在90年代初期!请提交它作为答案,以便我对其进行投票。所以是的,正确的答案是使用'open -a <application name>'
eric 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.