从命令行(Mac OS X)打开新的终端标签页

155

在Mac OS X的终端中,是否有可能从当前打开的标签页通过命令行打开一个新的标签页?

我知道在终端中打开新标签页的键盘快捷键是“CMD+t”,但我正在寻找一种基于脚本的解决方案,可以在命令行中执行。

15个回答

177

更新:此答案基于下面发布的shell函数而受到欢迎,该函数在OSX 10.10上仍然有效(除了-g选项)。
不过,现在有一个更全面、更强大、经过测试的脚本版本,可以在npm registry上作为CLI ttab使用,它还支持iTerm2

  • If you have Node.js installed, simply run:

    npm install -g ttab
    

    (depending on how you installed Node.js, you may have to prepend sudo).

  • Otherwise, follow these instructions.

  • Once installed, run ttab -h for concise usage information, or man ttab to view the manual.


在接受的答案基础上,以下是一个bash便捷函数,在当前终端窗口中打开一个新标签页,并可选择执行命令(额外提供了创建新窗口的变体函数)。

如果指定了命令,则其第一个标记将用作新标签页的标题。

示例调用:

    # Get command-line help.
newtab -h
    # Simpy open new tab.
newtab
    # Open new tab and execute command (quoted parameters are supported).
newtab ls -l "$Home/Library/Application Support"
    # Open a new tab with a given working directory and execute a command;
    # Double-quote the command passed to `eval` and use backslash-escaping inside.
newtab eval "cd ~/Library/Application\ Support; ls"
    # Open new tab, execute commands, close tab.
newtab eval "ls \$HOME/Library/Application\ Support; echo Press a key to exit.; read -s -n 1; exit"
    # Open new tab and execute script.
newtab /path/to/someScript
    # Open new tab, execute script, close tab.
newtab exec /path/to/someScript
    # Open new tab and execute script, but don't activate the new tab.
newtab -G /path/to/someScript

注意:当您从脚本中运行newtab(或newwin)时,脚本的初始工作文件夹将成为新标签/窗口中的工作文件夹,即使您在调用newtab/newwin之前在脚本中更改了工作文件夹-通过将evalcd命令一起传递作为解决方法(请参见上面的示例)。

源代码(粘贴到您的bash配置文件中,例如):

# Opens a new tab in the current Terminal window and optionally executes a command.
# When invoked via a function named 'newwin', opens a new Terminal *window* instead.
function newtab {

    # If this function was invoked directly by a function named 'newwin', we open a new *window* instead
    # of a new tab in the existing window.
    local funcName=$FUNCNAME
    local targetType='tab'
    local targetDesc='new tab in the active Terminal window'
    local makeTab=1
    case "${FUNCNAME[1]}" in
        newwin)
            makeTab=0
            funcName=${FUNCNAME[1]}
            targetType='window'
            targetDesc='new Terminal window'
            ;;
    esac

    # Command-line help.
    if [[ "$1" == '--help' || "$1" == '-h' ]]; then
        cat <<EOF
Synopsis:
    $funcName [-g|-G] [command [param1 ...]]

Description:
    Opens a $targetDesc and optionally executes a command.

    The new $targetType will run a login shell (i.e., load the user's shell profile) and inherit
    the working folder from this shell (the active Terminal tab).
    IMPORTANT: In scripts, \`$funcName\` *statically* inherits the working folder from the
    *invoking Terminal tab* at the time of script *invocation*, even if you change the
    working folder *inside* the script before invoking \`$funcName\`.

    -g (back*g*round) causes Terminal not to activate, but within Terminal, the new tab/window
      will become the active element.
    -G causes Terminal not to activate *and* the active element within Terminal not to change;
      i.e., the previously active window and tab stay active.

    NOTE: With -g or -G specified, for technical reasons, Terminal will still activate *briefly* when
    you create a new tab (creating a new window is not affected).

    When a command is specified, its first token will become the new ${targetType}'s title.
    Quoted parameters are handled properly.

    To specify multiple commands, use 'eval' followed by a single, *double*-quoted string
    in which the commands are separated by ';' Do NOT use backslash-escaped double quotes inside
    this string; rather, use backslash-escaping as needed.
    Use 'exit' as the last command to automatically close the tab when the command
    terminates; precede it with 'read -s -n 1' to wait for a keystroke first.

    Alternatively, pass a script name or path; prefix with 'exec' to automatically
    close the $targetType when the script terminates.

Examples:
    $funcName ls -l "\$Home/Library/Application Support"
    $funcName eval "ls \\\$HOME/Library/Application\ Support; echo Press a key to exit.; read -s -n 1; exit"
    $funcName /path/to/someScript
    $funcName exec /path/to/someScript
EOF
        return 0
    fi

    # Option-parameters loop.
    inBackground=0
    while (( $# )); do
        case "$1" in
            -g)
                inBackground=1
                ;;
            -G)
                inBackground=2
                ;;
            --) # Explicit end-of-options marker.
                shift   # Move to next param and proceed with data-parameter analysis below.
                break
                ;;
            -*) # An unrecognized switch.
                echo "$FUNCNAME: PARAMETER ERROR: Unrecognized option: '$1'. To force interpretation as non-option, precede with '--'. Use -h or --h for help." 1>&2 && return 2
                ;;
            *)  # 1st argument reached; proceed with argument-parameter analysis below.
                break
                ;;
        esac
        shift
    done

    # All remaining parameters, if any, make up the command to execute in the new tab/window.

    local CMD_PREFIX='tell application "Terminal" to do script'

        # Command for opening a new Terminal window (with a single, new tab).
    local CMD_NEWWIN=$CMD_PREFIX    # Curiously, simply executing 'do script' with no further arguments opens a new *window*.
        # Commands for opening a new tab in the current Terminal window.
        # Sadly, there is no direct way to open a new tab in an existing window, so we must activate Terminal first, then send a keyboard shortcut.
    local CMD_ACTIVATE='tell application "Terminal" to activate'
    local CMD_NEWTAB='tell application "System Events" to keystroke "t" using {command down}'
        # For use with -g: commands for saving and restoring the previous application
    local CMD_SAVE_ACTIVE_APPNAME='tell application "System Events" to set prevAppName to displayed name of first process whose frontmost is true'
    local CMD_REACTIVATE_PREV_APP='activate application prevAppName'
        # For use with -G: commands for saving and restoring the previous state within Terminal
    local CMD_SAVE_ACTIVE_WIN='tell application "Terminal" to set prevWin to front window'
    local CMD_REACTIVATE_PREV_WIN='set frontmost of prevWin to true'
    local CMD_SAVE_ACTIVE_TAB='tell application "Terminal" to set prevTab to (selected tab of front window)'
    local CMD_REACTIVATE_PREV_TAB='tell application "Terminal" to set selected of prevTab to true'

    if (( $# )); then # Command specified; open a new tab or window, then execute command.
            # Use the command's first token as the tab title.
        local tabTitle=$1
        case "$tabTitle" in
            exec|eval) # Use following token instead, if the 1st one is 'eval' or 'exec'.
                tabTitle=$(echo "$2" | awk '{ print $1 }') 
                ;;
            cd) # Use last path component of following token instead, if the 1st one is 'cd'
                tabTitle=$(basename "$2")
                ;;
        esac
        local CMD_SETTITLE="tell application \"Terminal\" to set custom title of front window to \"$tabTitle\""
            # The tricky part is to quote the command tokens properly when passing them to AppleScript:
            # Step 1: Quote all parameters (as needed) using printf '%q' - this will perform backslash-escaping.
        local quotedArgs=$(printf '%q ' "$@")
            # Step 2: Escape all backslashes again (by doubling them), because AppleScript expects that.
        local cmd="$CMD_PREFIX \"${quotedArgs//\\/\\\\}\""
            # Open new tab or window, execute command, and assign tab title.
            # '>/dev/null' suppresses AppleScript's output when it creates a new tab.
        if (( makeTab )); then
            if (( inBackground )); then
                # !! Sadly, because we must create a new tab by sending a keystroke to Terminal, we must briefly activate it, then reactivate the previously active application.
                if (( inBackground == 2 )); then # Restore the previously active tab after creating the new one.
                    osascript -e "$CMD_SAVE_ACTIVE_APPNAME" -e "$CMD_SAVE_ACTIVE_TAB" -e "$CMD_ACTIVATE" -e "$CMD_NEWTAB" -e "$cmd in front window" -e "$CMD_SETTITLE" -e "$CMD_REACTIVATE_PREV_APP" -e "$CMD_REACTIVATE_PREV_TAB" >/dev/null
                else
                    osascript -e "$CMD_SAVE_ACTIVE_APPNAME" -e "$CMD_ACTIVATE" -e "$CMD_NEWTAB" -e "$cmd in front window" -e "$CMD_SETTITLE" -e "$CMD_REACTIVATE_PREV_APP" >/dev/null
                fi
            else
                osascript -e "$CMD_ACTIVATE" -e "$CMD_NEWTAB" -e "$cmd in front window" -e "$CMD_SETTITLE" >/dev/null
            fi
        else # make *window*
            # Note: $CMD_NEWWIN is not needed, as $cmd implicitly creates a new window.
            if (( inBackground )); then
                # !! Sadly, because we must create a new tab by sending a keystroke to Terminal, we must briefly activate it, then reactivate the previously active application.
                if (( inBackground == 2 )); then # Restore the previously active window after creating the new one.
                    osascript -e "$CMD_SAVE_ACTIVE_WIN" -e "$cmd" -e "$CMD_SETTITLE" -e "$CMD_REACTIVATE_PREV_WIN" >/dev/null
                else
                    osascript -e "$cmd" -e "$CMD_SETTITLE" >/dev/null
                fi
            else
                    # Note: Even though we do not strictly need to activate Terminal first, we do it, as assigning the custom title to the 'front window' would otherwise sometimes target the wrong window.
                osascript -e "$CMD_ACTIVATE" -e "$cmd" -e "$CMD_SETTITLE" >/dev/null
            fi
        fi        
    else    # No command specified; simply open a new tab or window.
        if (( makeTab )); then
            if (( inBackground )); then
                # !! Sadly, because we must create a new tab by sending a keystroke to Terminal, we must briefly activate it, then reactivate the previously active application.
                if (( inBackground == 2 )); then # Restore the previously active tab after creating the new one.
                    osascript -e "$CMD_SAVE_ACTIVE_APPNAME" -e "$CMD_SAVE_ACTIVE_TAB" -e "$CMD_ACTIVATE" -e "$CMD_NEWTAB" -e "$CMD_REACTIVATE_PREV_APP" -e "$CMD_REACTIVATE_PREV_TAB" >/dev/null
                else
                    osascript -e "$CMD_SAVE_ACTIVE_APPNAME" -e "$CMD_ACTIVATE" -e "$CMD_NEWTAB" -e "$CMD_REACTIVATE_PREV_APP" >/dev/null
                fi
            else
                osascript -e "$CMD_ACTIVATE" -e "$CMD_NEWTAB" >/dev/null
            fi
        else # make *window*
            if (( inBackground )); then
                # !! Sadly, because we must create a new tab by sending a keystroke to Terminal, we must briefly activate it, then reactivate the previously active application.
                if (( inBackground == 2 )); then # Restore the previously active window after creating the new one.
                    osascript -e "$CMD_SAVE_ACTIVE_WIN" -e "$CMD_NEWWIN" -e "$CMD_REACTIVATE_PREV_WIN" >/dev/null
                else
                    osascript -e "$CMD_NEWWIN" >/dev/null
                fi
            else
                    # Note: Even though we do not strictly need to activate Terminal first, we do it so as to better visualize what is happening (the new window will appear stacked on top of an existing one).
                osascript -e "$CMD_ACTIVATE" -e "$CMD_NEWWIN" >/dev/null
            fi
        fi
    fi

}

# Opens a new Terminal window and optionally executes a command.
function newwin {
    newtab "$@" # Simply pass through to 'newtab', which will examine the call stack to see how it was invoked.
}

3
@jcollum 不客气,很高兴你觉得有用。我刚刚更新了帖子,加入了一个关于工作文件夹的警告,并更新了代码:添加了选项“-g”(创建新标签/窗口时不激活终端)和“-G”(不激活终端并且不改变终端内的活动标签),这对于在后台启动服务器等情况非常有帮助。请注意,以这种方式创建新标签时,终端仍然必须被短暂地激活,然后才能重新激活之前活动的应用程序。 - mklement0
1
@Leonardo 新标签页的工作目录与调用该函数的标签页相同。在调用“newtab”之前,在脚本中更改到不同的文件夹是行不通的。解决方法是通过eval语句传递一个cd命令给newtab;例如:newtab eval "cd ~/Library/Application\ Support; ls"。将传递给eval的整个命令用双引号括起来,并在内部使用反斜杠转义。 - mklement0
2
@IntegrityFirst:这是我学到的:使用 POSIX <name>() { ... } 函数语法使得 <name> 可能会受到 别名扩展 的影响,如果一个别名 <name> 恰好已定义,则会破坏函数定义(解析错误!)。 通常不是问题,因为在通常调用的脚本中,默认情况下关闭了别名扩展。 然而,在从交互式 shell 中源代码的脚本 - 例如在配置文件 / 初始化文件中 - 别名扩展是开启的。 解决方法:使用非 POSIX function <name> { ... } 语法来定义函数 - <name> 就不会受到别名扩展的影响。 - mklement0
1
谢谢!这个添加了一个带有case语句的“if [ "${BASH_SOURCE}" == "${0}" ]”,以便它可以被调用为脚本(例如“newtab.sh”,“newwin.sh”):https://gist.github.com/westurner/01b6be85e5a51fda22a6 - Wes Turner
1
@WesTurner:谢谢,这很有帮助;我实际上也通过npm注册表发布了一个脚本;请查看更新答案的顶部。 - mklement0
显示剩余5条评论

148

试试这个:

osascript -e 'tell application "Terminal" to activate' \
  -e 'tell application "System Events" to tell process "Terminal" to keystroke "t" using command down' \
  -e 'tell application "Terminal" to do script "echo hello" in selected tab of the front window'

但如果你需要运行动态命令,设置myCommand变量,并将最后一行替换为:

-e "tell application \"Terminal\" to do script \"${myCommand};\" in selected tab of the front window";

6
我该如何将新的命令(例如 echo hello)输入到这个新选项卡中? - ThomasReggi
25
在osascript命令的末尾添加-e 'tell application "Terminal" to do script "echo hello" in selected tab of the front window' - Gordon Davisson
@GordonDavisson:这个只在“Terminal”中有效,而不在“iTerm”中。 - clevertension
2
对于iTerm,@clevertension只需使用open -a iTerm ~/Applications/ - onmyway133
1
@Ciastopiekarz 你是指在新打开的标签页中吗?使用我对ThomasReggi的回答相同的方法:添加-e 'tell application "Terminal" to do script "cd /path/to/target/directory" in selected tab of the front window'。请注意,如果路径来自变量,则需要使用双引号字符串而不是单引号,并转义内部引用的字符串,以及可能需要转义路径本身。 - Gordon Davisson
显示剩余4条评论

25
osascript -e 'tell app "Terminal"
   do script "echo hello"
end tell'

这将打开一个新终端并在其中执行命令“echo hello”。


7
可以翻译为:"这个方法能够实现,但是新标签页会在一个单独的终端实例中创建。有没有办法让新标签页留在当前终端实例中呢?" - Calvin Cheng
1
顺带说一句,你可以使用空字符串 do script "" 来创建一个新的终端而不需要发出命令。 - Chris Page

20

以下是bash_it的实现方法:

function tab() {
  osascript 2>/dev/null <<EOF
    tell application "System Events"
      tell process "Terminal" to keystroke "t" using command down
    end
    tell application "Terminal"
      activate
      do script with command "cd \"$PWD\"; $*" in window 1
    end tell
EOF
}

在您的 .bash_profile 中添加此内容后,您将使用 tab 命令在新标签页中打开当前工作目录。
参见:https://github.com/revans/bash-it/blob/master/plugins/available/osx.plugin.bash#L3

1
非常有帮助。在我的.bash_profile中使用它,我能够自动启动一堆选项卡并自动ssh到它们。当然,我已经启用了ssh密钥对认证。 - Sandeep Kanabar

18
如果您使用oh-my-zsh(每个时髦的极客都应该使用),在.zshrc中激活macos插件后,只需输入tab命令即可打开一个新标签页并cd到您所在的目录。

zsh 管理着更多旧的垃圾,以至于失去了控制。 - Tegra Detra
1
你能提供更多信息吗?什么是tab命令?输入“tab”似乎没有任何反应。 - png
1
@Solvitieg 它已经9年了,现在可能已经过时了。 - CharlesB
4
不过时,只需启用并使用它。请确保在您的".zshrc"文件中启用"osx"插件。 - Chase Giunta
2
osx已经迁移到macos--https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/macos - keithpjolley
显示剩余3条评论

8
我将这些内容添加到我的.bash_profile中,以便我可以访问tabname和newtab。
tabname() {
  printf "\e]1;$1\a"
}

new_tab() {
  TAB_NAME=$1
  COMMAND=$2
  osascript \
    -e "tell application \"Terminal\"" \
    -e "tell application \"System Events\" to keystroke \"t\" using {command down}" \
    -e "do script \"printf '\\\e]1;$TAB_NAME\\\a'; $COMMAND\" in front window" \
    -e "end tell" > /dev/null
}

所以当你在特定的选项卡上时,你只需输入即可。
tabname "New TabName"

这是一个能够整理所有已打开标签页的工具。使用这个工具比在标签页上获取信息并进行更改要好得多。


谢谢。您知道如何在从选项卡进行 SSH 并从 SSH 会话退出后保留选项卡名称吗? - anjanb

8

我知道这是一篇旧帖子,但对我有用:

open -a Terminal "`pwd`"

按照下面的要求运行命令需要一些技巧:

echo /sbin/ping 8.8.8.8 > /tmp/tmp.sh;chmod a+x /tmp/tmp.sh;open -a Terminal /tmp/tmp.sh

非常好!如果我想传递在新终端实例中运行的命令,该怎么做呢? :D - Strazan
@Strazan刚刚编辑了上面的回答... 玩得愉快!看起来终端会接受那样的参数... - neophytte
1
当我尝试这样做时,它会打开一个新的终端窗口而不是在现有的选项卡上打开一个新的。 - wytten
你运行的是什么操作系统和终端?今天在Darwin Kernel Version 20.3.0和Terminal Version 2.11 (440)上为我工作。 - neophytte
从脚本运行此命令时,无法通过用户控制终端(期望标准输入或其他)。 - Nir O.

6
键盘快捷键cmd-t打开一个新的选项卡,您可以按照以下方式将此按键传递给OSA命令: osascript -e 'tell application "System Events"' -e 'keystroke "t" using command down' -e 'end tell'

3
open -n -a Terminal

"原始答案",并且您可以将目标目录作为参数传递。"
open -n -a Terminal /Users

3
这让我打开了一个新窗口,而不是一个标签页。 - stack-delay

2

当您在终端窗口中时, command + n => 打开一个新的终端 command + t => 在当前终端窗口中打开一个新选项卡


2
这需要从命令行工作。基本上是一个脚本,因为它是一个重复性的任务。 - Gianfranco P.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接