当我的当前选项卡被一个长时间运行的进程占用时,我经常需要在与当前选项卡相同的目录中打开一个新选项卡来执行其他操作。但是,默认情况下,当您创建新选项卡时,Terminal.app 会从 ~/ 开始。有没有什么办法让它自动跳转?
答案1
在 OS X 10.7 (Lion) 中,Terminal.app 原生支持此功能:New Windows/Tabs open in: Same working directory
答案2
在不同环境传递字符串时必须非常小心。
我运行的是 10.4,所以我的“tfork”脚本总是打开一个新窗口。调整它以使用选项卡应该很容易:
#!/bin/sh
# source: http://www.pycs.net/bob/weblog/2004/02/23.html#P49
# Rewritten to use osascript args -> run handler args.
# Added ability to pass additional initial command and args to new shell.
# Bug: Non ASCII characters are unreliable on Tiger.
# Tiger's osascript seems to expect args to be encoded in
# the system's primary encoding (e.g. MacRoman).
# Once in AppleScript, they are handled OK. Terminal sends them
# back to the shell as UTF-8.
test $# -eq 0 && set -- : # default command if none given
osascript - "$(pwd)" "$@" <<\EOF
on run args
set dir to quoted form of (first item of args)
set cmd_strs to {}
repeat with cmd_str in rest of args
set end of cmd_strs to quoted form of cmd_str
end
set text item delimiters to " "
set cmd to cmd_strs as Unicode text
tell app "Terminal" to do script "cd " & dir & " && " & cmd
end
EOF
例子:tfork git log -p ..FETCH_HEAD
修正案:已经运行的进程的 cwd“占用”终端标签
“程序的当前目录占据当前选项卡”的想法并不像人们想象的那么明显。
每个终端tab 有一个单独的 tty 设备,由它运行的进程使用(最初是一个 shell;此后,无论 shell 启动什么)。
每个(正常)终端tty 有一个前台进程组,人们可能认为它“占用”了 tty。
每个进程组中可以有多个进程。
每个进程可以有自己的当前工作目录 (cwd)(某些环境为每个线程提供自己的 cwd 或 cwd 等效项,但我们将忽略这一点)。
上述事实建立了一种从 tty 到 cwd 的路径:tty -> 前台进程组 -> 前台进程组的进程 -> cwds。
问题的第一部分(从 tty 到前台进程)可以通过以下输出解决:附言:
ps -o tty,pid,tpgid,pgid,state,command | awk 'BEGIN{t=ARGV[1];ARGC=1} $1==t && $3==$4 {print $2}' ttyp6
(其中“ttyp6”是感兴趣的 tty 的名称)
可以使用以下方法将进程 (PID) 映射到 cwdlsof:
lsof -F 0n -a -p 2515,2516 -d cwd
(其中“2515,2516”是以逗号分隔的感兴趣的进程列表)
但在 Tiger 下,我看不到直接获取特定 tty 设备名称的方法终端窗口。在 Tiger 中,获取 tty 名称的方式非常丑陋。也许 Leopard 或 Snow Leopard 可以做得更好。
我将其全部放在 AppleScript 中,如下所示:
on run
(* Find the tty. *)
-- This is ugly. But is seems to work on Tiger. Maybe newer releases can do better.
tell application "Terminal"
set w to window 1
tell w
set origName to name
set title displays device name to not title displays device name
set newName to name
set title displays device name to not title displays device name
end tell
end tell
set tty to extractTTY(origName, newName)
if tty is "" then
display dialog "Could not find the tty for of the current Terminal window." buttons "Cancel" cancel button "Cancel" default button "Cancel"
end if
(* Find the PIDs of the processes in the foreground process group on that tty. *)
set pids to paragraphs of (do shell script "
ps -o pid,tty,tpgid,pgid,state,command |
awk '
BEGIN {t=ARGV[1];ARGC=1}
$2==t && $3==$4 {print $1}
' " & quoted form of tty)
if pids is {} or pids is {""} then
display dialog "Could not find the processes for " & tty & "." buttons "Cancel" cancel button "Cancel" default button "Cancel"
end if
(* Find the unique cwds of those processes. *)
set text item delimiters to {","}
set lsof to do shell script "lsof -F 0n -a -d cwd -p " & quoted form of (pids as Unicode text) without altering line endings
set text item delimiters to {(ASCII character 0) & (ASCII character 10)}
set cwds to {}
repeat with lsofItem in text items of lsof
if lsofItem starts with "n" then
set cwd to text 2 through end of lsofItem
if cwds does not contain cwd then ¬
set end of cwds to cwd
end if
end repeat
if cwds is {} then
display dialog "No cwds found!?" buttons "Cancel" cancel button "Cancel" default button "Cancel"
end if
if length of cwds is greater than 1 then
set cwds to choose from list cwds with title "Multiple Distinct CWDs" with prompt "Choose the directory to use:" without multiple selections allowed and empty selection allowed
if cwds is false then error number -128 -- cancel
end if
(* Open a new Terminal. *)
tell application "Terminal" to do script "cd " & quoted form of item 1 of cwds
end run
to extractTTY(a, b)
set str to textLeftAfterRemovingMatchingHeadAndTail(a, b)
set offs to offset of "tty" in str
if offs > 0 then
return text offs through (offs + 4) of str
end if
return ""
end extractTTY
to textLeftAfterRemovingMatchingHeadAndTail(big, little)
set text item delimiters to space
if class of big is not list then set big to text items of big
if class of little is not list then set little to text items of little
set {maxLen, minLen} to {length of big, length of little}
if maxLen < minLen then ¬
set {big, little, maxLen, minLen} to {little, big, minLen, maxLen}
set start to missing value
repeat with i from 1 to minLen
if item i of big is not equal to item i of little then
set start to i
exit repeat
end if
end repeat
if start is missing value then
if maxLen is equal to minLen then
return ""
else
return items (minLen + 1) through end of big as Unicode text
end if
end if
set finish to missing value
repeat with i from -1 to -minLen by -1
if item i of big is not equal to item i of little then
set finish to i
exit repeat
end if
end repeat
if finish is missing value then set finish to -(minLen + 1)
return items start through finish of big as Unicode text
end textLeftAfterRemovingMatchingHeadAndTail
使用以下方式保存脚本编辑器(AppleScript 编辑器在 Snow Leopard 中)并使用启动器(例如快速脚本)将其分配给一个键(或者直接从 AppleScript 菜单运行它(通过/应用程序/AppleScript/AppleScript 实用程序.app))。
答案3
我有发布了脚本使用克里斯·约翰森上面的代码和另一个脚本使用当前设置在当前目录中打开新选项卡,主要是因为我对终端进行了颜色协调。谢谢 Chris,对于这个脚本,我已经使用了几个月,它节省了很多时间。
(* 此脚本会在当前选项卡的目录中打开一个新的 Terminal.app 选项卡,并使用相同的设置。如果您还没有这样做,您需要按照此处所述启用辅助设备的访问权限:http://www.macosxautomation.com/applescript/uiscripting/index.html
这几乎是两个脚本共同完成的工作,感谢它们:
Chris Johnsen 的脚本在当前目录中打开一个新选项卡:OS X Terminal.app:如何在与当前选项卡相同的目录中启动新选项卡?
Jacob Rus 的“menu_click”允许我使用相同的设置创建选项卡,但终端的 API 却不允许:http://hints.macworld.com/article.php?story=20060921045743404
如果您更改终端配置文件的名称,AppleScript API 将返回旧名称,直到您重新启动应用程序,因此在此之前,脚本将无法在重命名的设置上工作。唉。此外,必须激活终端才能执行菜单命令,这会将所有终端窗口置于最前面。
*)
-- from http://hints.macworld.com/article.php?story=20060921045743404
-- `menu_click`, by Jacob Rus, September 2006
--
-- Accepts a list of form: `{"Finder", "View", "Arrange By", "Date"}`
-- Execute the specified menu item. In this case, assuming the Finder
-- is the active application, arranging the frontmost folder by date.
on menu_click(mList)
local appName, topMenu, r
-- Validate our input
if mList's length < 3 then error "Menu list is not long enough"
-- Set these variables for clarity and brevity later on
set {appName, topMenu} to (items 1 through 2 of mList)
set r to (items 3 through (mList's length) of mList)
-- This overly-long line calls the menu_recurse function with
-- two arguments: r, and a reference to the top-level menu
tell application "System Events" to my menu_click_recurse(r, ((process appName)'s ¬
(menu bar 1)'s (menu bar item topMenu)'s (menu topMenu)))
end menu_click
on menu_click_recurse(mList, parentObject)
local f, r
-- `f` = first item, `r` = rest of items
set f to item 1 of mList
if mList's length > 1 then set r to (items 2 through (mList's length) of mList)
-- either actually click the menu item, or recurse again
tell application "System Events"
if mList's length is 1 then
click parentObject's menu item f
else
my menu_click_recurse(r, (parentObject's (menu item f)'s (menu f)))
end if
end tell
end menu_click_recurse
-- with the noted slight modification, from https://superuser.com/questions/61149/os-x-terminal-app-how-to-start-a-new-tab-in-the-same-directory-as-the-current-ta/61264#61264
on run
(* Find the tty. *)
-- This is ugly. But is seems to work on Tiger. Maybe newer releases can do better.
tell application "Terminal"
set w to the front window
tell w
set origName to name
set title displays device name to not title displays device name
set newName to name
set title displays device name to not title displays device name
end tell
end tell
set tty to extractTTY(origName, newName)
if tty is "" then
display dialog "Could not find the tty for of the current Terminal window." buttons "Cancel" cancel button "Cancel" default button "Cancel"
end if
(* Find the PIDs of the processes in the foreground process group on that tty. *)
set pids to paragraphs of (do shell script "
ps -o pid,tty,tpgid,pgid,state,command |
awk '
BEGIN {t=ARGV[1];ARGC=1}
$2==t && $3==$4 {print $1}
' " & quoted form of tty)
if pids is {} or pids is {""} then
display dialog "Could not find the processes for " & tty & "." buttons "Cancel" cancel button "Cancel" default button "Cancel"
end if
(* Find the unique cwds of those processes. *)
set text item delimiters to {","}
set lsof to do shell script "lsof -F 0n -a -d cwd -p " & quoted form of (pids as Unicode text) without altering line endings
set text item delimiters to {(ASCII character 0) & (ASCII character 10)}
set cwds to {}
repeat with lsofItem in text items of lsof
if lsofItem starts with "n" then
set cwd to text 2 through end of lsofItem
if cwds does not contain cwd then ¬
set end of cwds to cwd
end if
end repeat
if cwds is {} then
display dialog "No cwds found!?" buttons "Cancel" cancel button "Cancel" default button "Cancel"
end if
if length of cwds is greater than 1 then
set cwds to choose from list cwds with title "Multiple Distinct CWDs" with prompt "Choose the directory to use:" without multiple selections allowed and empty selection allowed
if cwds is false then error number -128 -- cancel
end if
(* Open a new Terminal. *)
-- Here is where I substituted the menu_click call to use the current settings
tell application "Terminal"
activate
tell window 1
set settings to name of current settings in selected tab
end tell
end tell
menu_click({"Terminal", "Shell", "New Tab", settings})
tell application "Terminal" to do script "cd " & quoted form of item 1 of cwds in selected tab of window 1
end run
to extractTTY(a, b)
set str to textLeftAfterRemovingMatchingHeadAndTail(a, b)
set offs to offset of "tty" in str
if offs > 0 then
return text offs through (offs + 6) of str
end if
return ""
end extractTTY
to textLeftAfterRemovingMatchingHeadAndTail(big, little)
set text item delimiters to space
if class of big is not list then set big to text items of big
if class of little is not list then set little to text items of little
set {maxLen, minLen} to {length of big, length of little}
if maxLen < minLen then ¬
set {big, little, maxLen, minLen} to {little, big, minLen, maxLen}
set start to missing value
repeat with i from 1 to minLen
if item i of big is not equal to item i of little then
set start to i
exit repeat
end if
end repeat
if start is missing value then
if maxLen is equal to minLen then
return ""
else
return items (minLen + 1) through end of big as Unicode text
end if
end if
set finish to missing value
repeat with i from -1 to -minLen by -1
if item i of big is not equal to item i of little then
set finish to i
exit repeat
end if
end repeat
if finish is missing value then set finish to -(minLen + 1)
return items start through finish of big as Unicode text
end textLeftAfterRemovingMatchingHeadAndTail
答案4
我使用这个别名/shell 脚本来执行此操作。
# modified from http://www.nanoant.com/programming/opening-specified-path-in-terminals-new-tab
alias twd=new_terminal_working_directory
function new_terminal_working_directory() {
osascript <<END
tell application "Terminal"
tell application "System Events" to tell process "Terminal" to keystroke "t" using command down
do script "cd $(pwd)" in first window
end tell
END
}