Terminal: CD into the Active Finder Window's Folder

I often work with Terminal.

Getting into the right directory annoyed me every time. Mostly I had to drag the current folder from the Finder into Terminal.

So I built this little macro.

Works like this:

  1. In my Terminal window, I type "ppath"
  2. KBM grabs the path of the frontmost Finder window
  3. KBM executes "cd %Variable%path%" in my Terminal window

P.S. Only later did I realize that there are other solutions out there. Didn't try those, though, since fixing it with KBM was so easy.

Terminal - Set Finder Path.kmmacros (3.4 KB)

Where you’ve asked Finder to give you the location pointing to the front Finder window, or, if one doesn’t exist, then to default to the desktop, there’s a specific property that does exactly this called insertion location, which means you can replace the entire tell block at the start with a single line:

tell application "Finder" to get the insertion location as alias

Then it struck me how you started with the bulk of the macro depending on an AppleScript, before switching to other actions to do what would seem to me to be quicker and simpler to do with the AppleScript, since it is there anyway.

Here’s the entire macro performed by a script in three lines (I’ve used the continuation character to spread it over a few lines for readability):

tell application "Finder" to get the ¬
	quoted form of the ¬
	POSIX path of the ¬
	(insertion location as text)

set shellCmd to "cd " & result

tell application "Terminal" to tell the front window ¬
	to do script shellCmd in ¬
	the selected tab

Another thought, given you spend a lot of time in Terminal, would be to define an alias or function in Terminal called ppath that would execute the first line of the AppleScript to obtain the insertion location then do a simple cd that would complete the process:

cd "$(osascript \
-e 'tell application "Finder" to ¬' \
-e 'get the POSIX path of ¬' \
-e '(the insertion location as text)')"
1 Like

Great script! Thanks for sharing.

For us bash-challenged folks, would you mind showing us exactly how to do that?

Thanks.

Hi @JMichaelTX,

Thank you very much.

Sure. So the scripting portion I did already, which looks like this:

cd "$(osascript \
-e 'tell application "Finder" to ¬' \
-e 'get the POSIX path of ¬' \
-e '(the insertion location as text)')"

Breaking it down (for the benefit of anyone else wishing to learn what's happened here), there are two separate commands used here:

  • osascript: This is the shell program that executes AppleScripts (or JavaScripts) either by way of a script file that is provided as the argument (which isn't done here, because I'm not using a script file), or by way of plain text specified line-by-line by each use of the -e option. You can use the -e option as many times as you like, with each occurrence being one line of AppleScript code that combine to form a complete script. Alternatively, you can use a single -e option, the write the text out fully between a pair of opening and closing quotes that includes linefeeds, etc:
osascript -e "tell application \"Finder\" to ¬
get the POSIX path of ¬
(the insertion location as text)"

The result of the script is written to stdout, which in my case was /Users/CK/Downloads/Safari Downloads/

  • cd: The built-in bash command that lets one set the present working directory to the path specified by the argument supplied, e.g. cd ~/Downloads to be now working in the Downloads directory of my home folder.

In addition to these two commands, there's a special bash pseudo-"function" of sorts (really more of an expression or operator) that allows one to perform a command substitution, which is where a shell command can have its output be substituted in place of an argument to be used by another command:

  • $(...): This syntax is a dollar ($) symbol following by a pair of opening and closing parentheses, between which one can write a regular shell command, which the ellipsis represents here. The shell then runs the command inside the parentheses, and whatever result it returned by the command gets pasted in place of the syntax. For example, cd $(echo ~/Downloads) has the same ultimate outcome as cd ~/Downloads, but uses echo that would typically print the text "~/Downloads" to stdout, which in this situation means it gets returned to be substituted in place of the $() and become the argument supplied to cd.

My use of it in the snippet takes the form cd $(osascript ...). As in the previous example, osascript inside the paranetheses gets run as it would normally by the shell, and the output of that script when executed independently was, I stated before, /Users/CK/Downloads/Safari Downloads/. This then serves as a replacement to $() and the substituted path then becomes the argument upon which cd acts, taking me to the Safari Downloads folder, just in the same way as it would had I written the command explicitly as cd "/Users/CK/Downloads/Safari Downloads/".

Although, note here I've used double quotes to encapsulate the string so it is read as a single argument, otherwise the space in the word Safari Downloads would serve to separate it into two separate arguments, and the command would fail because I don't have a folder called Safari in my Downloads folder, nor one called Downloads in the directory I was presently working in.

I also, therefore, was sure that I included the quotes even when using the command substitution syntax, and the actual form of the command is cd "$(osascript ...)".

Aliases and Functions

Functions

Now, for what I left out in my original reply, and the part that addresses you question, which relates to my suggestion that an alias or function be defined that can store the command and be executed whenever the name of the alias or function is entered into the command line.

To define ppath as a function in bash, you would declare it like this:

function ppath {
    cd "$(osascript \
    -e 'tell application "Finder" to ¬' \
    -e 'get the POSIX path of ¬' \
    -e '(the insertion location as text)')"
}

Then, for as long as the instance of the current shell is running, you can type ppath and use it just like any other command line tool, the effect of which is to cd me into the directory output from the osascript.

However, when the shell session ends, the function no longer exists, and won't be available when one next opens Terminal.

The two methods of which I'm aware to store a function and have it remain available to use at any time in the future and across all shell instances is:

① To have the function become part of a bash script that is saved as a file, then put in one of the directories specified by the $PATH environment variable, which tells you where the shell searches to find the executable script files whenever a command is called upon. The file would look like this:

#!/bin/bash

cd "$(osascript \
-e 'tell application "Finder" to ¬' \
-e 'get the POSIX path of ¬' \
-e '(the insertion location as text)')"

and would be saved simply as ppath (no file extension is needed). Then it is made executable by running the command: chmod +x /path/to/ppath. Finally, it is moved into a directory that allows bash to find it without having to type out the full path to the file. In my case, I moved it to /usr/local/bin to run a test.

② The other way, is to store the function declaration inside ~/.bashrc, which is used to initialise each instance of the shell when it's first created. Therefore, it'll ensure the function is freshly defined and available to be called whenever a new shell is opened. If the ~/.bashrc file doesn't exist, you can create it.

The difference in how these two options have ppath declared is in the use or omission of the function command: if saving it as a file, you can see I omitted it. It's not needed in such a simple script, so remains in the main body of the file. The filename becomes the label used to call on the command to execute. Conversely, if you're adding the definition of ppath to ~/.bashrc, you need to use the function command to provide a wrapper to contain the commands and separate them from other functions you might have defined in that file. So that's where you define function ppath { ... }, where it's the function name that provides the label with which to call upon and execute the commands contained within.

Aliases

As the name implies, an alias in bash is essentially a shortcut for commonly used terms, enabling one to define a synonym that refers to another command in the shell. Often it's used to help reduce the amount one has to type by using aliases that have short names refer to a longer command expression.

The declaration is in the form of alias ⟨name⟩="⟨command⟩", where alias is the literal command used to create an alias for a given command ⟨command⟩ and have it assigned to the label ⟨name⟩ with which one wishes to use to invoke the command's execution.

For example, find has an annoyingly long and wordy syntax to achieve anything useful, and I often might be typing out the same lengthy command to find files in a particular folder in Finder that are over 4 weeks old, with either a .jpg or .mov file extension, and either having been exported as a movie or it having extended attributes (which includes Finder tags)

find "/Users/CK/Pictures/Photo Booth Library/Pictures" -depth 1 -type f -Btime +4w \
\( -name '*.jpg' -or -name '*.mov' \) \( -name '*.export.mov' -or -xattr \)

Rather than declaring a function, which is a bit overkill given the complex things one can do with a function, but not need to do in this instance, an alias is a lot more limited in what it can be used for, but also very simple and made-for-purpose.

alias oldPhotos="find '/Users/CK/Pictures/Photo Booth Library/Pictures' \
                      -depth 1 -type f -Btime +4w \
                      \( -name '*.jpg' -or -name '*.mov' \) \
                      \( -name '*.export.mov' -or -xattr \)"

Now the same command can be called by typing oldPhotos.

Here's a nice page I came across demonstrating some practical uses for alias, which isn't always just about reducing one's typing efforts.

Now to implement it with the original bash command relating to this thread, one would definte ppath as an alias like this:

alias ppath='cd "$(osascript <<<"tell application \"Finder\" to get the POSIX path of (the insertion location as text)")"'

I've altered the way I supplied the arguments to osascript here, utilising a here string, denoted by <<<, which passes the string to the right into the standard input of the command on the left as an argument. There's another similar notation of the form <<, called a heredoc, which is for delivering multi-line input, essentially enabling one to author an entire file on the command line and have it delivered as an argument, as one would do by supplying a path to a file, but instead chooses to supply its contents.

alias ppath='cd "$(osascript <<OSA
tell application "Finder" to return ¬
the POSIX path of ¬
(the insertion location as text)
OSA
)"'

The main reason for changing it for the alias declaration was because there were a lot of quotes being used in its original form, and it was tough trying to get them to open and close happily, and a here string and heredoc each eliminate a layer of quotation marks (though there are more important reasons one might employ them in other places, mostly in considering how and when new shell processes get spawned depending on the way a chain of commands is formed).

As before when declaring the function on the command line, the lifespan of the alias will be however long that shell remains running. In order to get ut initialised each time a new shell is created, add the declaration to the ~/.bashrc file.


I hope that helps you and anybody else in some way. I apologise it's such a lengthy reply, but I didn't know how much detail was appropriate, so approached from an assumption that a reader may have almost zero Bash experience, which I know isn't the case for you or the OP.

3 Likes

Thanks, @CJK. I'm sure your lengthly explanation will benefit many over time. All I was interested in was the notion of functions and aliases for use within Terminal. If neither persist across sessions or I have to create a file, then that negates the benefit for me. I'll just use the AppleScript using either KM or FastScripts.

Well, darn it.

Aside: I actually purchased FastScripts off the back of a really in-depth post you wrote about its myriad benefits and how it's an essential part of your automation and computer usage, plus how it compared to and was justifiably useful independently of Keyboard Maestro (I had assumed, up to that point, that FastScripts was simply a slightly better AppleScript menu, and a much poorer Keyboard Maestro).

What shell do you use ? I'm going to assume it's bash unless you say otherwise, in which case I am not aware of a way to make declarations like functions, aliases, or variables persist without using ~/.bashrc or some other file. I did a quick Google and what I just read seemed to suggest I was speaking truthfully.

However, I actually use FiSh as my default shell, and I recommend having a look into that if you're at all interested in working with the command line, but perhaps wanted a different flavour. There are many reasons why I like FiSh over Bash (and a few why Bash has an edge in some places), but notably, it's easy to create functions, aliases, and variables and have them persist across shells. In fact, everything in FiSh is basically done in terms of functions, which I find very intuitive.

Don't worry about me. I'm sure many others will benefit from your detailed discussion.

You're talking to a shell scripting baby here -- I can't even spell "shel". LOL

I assume I'm using bash since I've never done anything to change it. I just open the "Terminal.app", or more often, use the KM Execute Shell Script.

So, please feel free to carry on if you wish -- but don't do so for my benefit. :wink:

If you have some good tips you'd like to share, then I'd suggest a post in the Tips & Tutorials Category.

You can get the Finder Insertion Location with the FinderInsertionLocation token.

4 Likes

Hey Guys,

Thread:

Open a new Terminal window for the current Finder folder

My post -- CD into the front Finder window

Latest script:


CD to the frontmost folder in the Finder.


I run this one with O in FastScripts. (Seriously fast.)

----------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2011/10/16 02:11
# dMod: 2018/04/26 00:28
# Appl: Terminal
# Task: CD to Front Finder Window's Directory – if tab is busy then create a new window.
#     : Normally run from FastScripts - https://www.red-sweater.com/fastscripts/
# Libs: None
# Osax: None 
# Tags: @Applescript, @Terminal, @CD, @Front, @Finder, @Directory, @Insertion_Location
----------------------------------------------------------------

# Normal script has no error-handler due to FastScripts' better error dialog.

# Error-handling added for this posting of the script.

----------------------------------------------------------------

try
   
   tell application "Finder"
      if (front window exists) and (front window's name starts with "Searching “") then
         error "Error:" & linefeed & linefeed & "Front window is a Spotlight Search window!"
      else
         set tScript to "cd " & quoted form of (POSIX path of (insertion location as text))
      end if
   end tell
   
   tell application "Terminal"
      tell selected tab of front window
         if its busy = false then
            do script tScript in it
         else
            do script tScript
         end if
      end tell
   end tell
   
on error e number n
   set e to e & return & return & "Num: " & n
   if n ≠ -128 then
      try
         tell application (path to frontmost application as text) to set ddButton to button returned of ¬
            (display dialog e with title "ERROR!" buttons {"Copy Error Message", "Cancel", "OK"} ¬
               default button "OK" giving up after 30)
         if ddButton = "Copy Error Message" then set the clipboard to e
      end try
   end if
end try

----------------------------------------------------------------


CD to the selected folder in the Finder.


I run this one with O in FastScripts.

I should probably support a selected file in addition to a selected folder, but I haven't yet bothered.

----------------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2011/10/16 02:11
# dMod: 2018/04/07 23:14
# Appl: Terminal
# Task: CD to selected folder or app in the Finder – if tab is busy then create a new window.
#     : Normally run from FastScripts - https://www.red-sweater.com/fastscripts/
# Libs: None
# Osax: None 
# Tags: @Applescript, @Terminal, @CD, @Front, @Finder, @App, @Directory
----------------------------------------------------------------

# Normal script has no error-handler due to FastScripts' better error dialog.

# Error-handling added for this posting of the script.

----------------------------------------------------------------

try
   
   tell application "Finder"
      
      set finderSelectionList to selection as alias list
      if length of finderSelectionList = 0 then error "No files were selected in the Finder!"
      set theItem to item 1 of finderSelectionList
      
      if kind of (item theItem) is in {"Application", "Folder"} then
         set itemPath to POSIX path of (theItem as text)
      else
         error "Error:" & linefeed & linefeed & "Improper item selected in the Finder!"
      end if
      
   end tell
   
   set shCMD to "cd " & quoted form of itemPath
   
   tell application "Terminal"
      tell selected tab of front window
         if its busy = false then
            do script shCMD in it
         else
            do script shCMD
         end if
      end tell
   end tell
   
on error e number n
   set e to e & return & return & "Num: " & n
   if n ≠ -128 then
      try
         tell application (path to frontmost application as text) to set ddButton to button returned of ¬
            (display dialog e with title "ERROR!" buttons {"Copy Error Message", "Cancel", "OK"} ¬
               default button "OK" giving up after 30)
         if ddButton = "Copy Error Message" then set the clipboard to e
      end try
   end if
end try

----------------------------------------------------------------


Bash Function to CD into the Front Finder Window:


NOTE – this is a modification for your shell environment and must be placed in:

~/.profile

OR

~/.bash_profile

Personally I use the .profile file.

Once installed you have to re-source any open Terminal windows — OR — restart the Terminal app — to make it see the changes made to the profile.

Then just type ff<return> in any Terminal window to get to the front Finder window.

#====================================================================

# Task: cd the Terminal into the front Finder Window
# dCre: 2014/10/15 10:16
# dMod: 2018/04/26 00:37

function ff {
DIR=$(osascript -e '
tell application "Finder"
   if (count Finder windows) > 0 then
      if name of front window does not start with "Searching" then
         set _path to target of front window as alias
      else
         error "Front window is a Spotlight Search window!"
      end if
   else
      set _path to path to desktop
   end if
end tell
set _path to (POSIX path of _path)
');
cd "$DIR";
}

#====================================================================

-Chris

1 Like

One more variant for this theme (in JavaScript).

For when the Finder is your starting point, and you need a new Terminal window for the Finder's working directory:

Terminal window for working directory of Finder.kmmacros (19.4 KB)

JS source
(() => {
    'use strict';

    // ---------------------- MAIN -----------------------
    const main = () => {
        const fp = finderPath();
        return (
            // Terminal window context.
            Application('Terminal')
            .doScript(`cd "${fp}"; clear`),

            // JavaScript interpreter context.
            // (e.g. for embedding in a notification).
            fp
        );
    };

    // --------------------- GENERIC ---------------------
    // finderPath :: () -> IO ()
    const finderPath = () =>
        decodeURI(
            Application('Finder')
            .insertionLocation()
            .url()
        )
        .slice(7);

    // MAIN --
    return main();
})();

Rob - How did you get the triangle toggle thingy for the JS source?

The secret lies herein ...

[details="Summary"]
This text will be hidden
[/details]
3 Likes