462

Is there an equivalent to the Ubuntu tree command for Mac OS X?

0

14 Answers 14

653

You can get the tree command on macOS, too. If you have Homebrew:

brew install tree

If you do not have Homebrew installed, try one approach below.


Installing a package manager approach

Follow the instructions on these websites to install Homebrew, MacPorts, or Fink. Do not install more than one package manager at the same time!

Follow the prompt for whichever you installed.

For Homebrew: brew install tree

For MacPorts: sudo port install tree

For Fink: fink install tree

Installing from source approach

  1. Install the Xcode command line tools by running xcode-select --install.

  2. Download the tree source

  3. Change the Makefile to get it to work, which is also explained in @apuche's answer below. Commenting out the Linux options and uncommenting the macOS options should be enough.

  4. Then, run ./configure, then make.

  5. Now you have to move the tree binary file to a location that's in your executable path. For example:

     sudo mkdir -p /usr/local/bin
     sudo cp tree /usr/local/bin/tree
    
  6. Now edit ~/.bash_profile to include:

     export PATH="/usr/local/bin:$PATH"
    
  7. Reload the shell, and now which tree should point to /usr/local/bin/tree.

16
  • shaunchapmanblog.com/post/329270449/… also seems to have detailed instructions but may cause issues with llvm-gcc living under /Developer if you are running Xcode 4.x; a bit of fiddling around should do the trick. Nov 21, 2011 at 11:04
  • 1
    @DavidMoles Because simply running make on OS X doesn't work. First, you have to know how to install the Xcode command-line tools (or generally, have some idea about building software) and then you'll find that it errors out on an undefined symbol. So you have to do some adjustments to the makefile (as explained here). Too much hassle IMO.
    – slhck
    Jan 21, 2015 at 22:04
  • 4
    @7stud Many people know a few command line tools for very simple CLI tasks, and they may not know how to compile software. People sometimes struggle to understand what ./configure and make exactly do and why those are needed in the first place. Or they don't want to deal with it. Or read any help files, for that matter. They'd rather do something like apt-get install. It's fine if you prefer installations from source (and I personally do, too), but you have to accept that there are solutions that are perceived easier by others, or perhaps even the majority of normal computer users.
    – slhck
    Apr 9, 2015 at 6:56
  • 2
    People sometimes struggle to understand what ./configure and make exactly do and why those are needed in the first place I've been installing software from source for 15 years, and I have no idea what ./configure and make do. I just know they are steps I need to perform in order to install software. I blindly read the README and INSTALL files, and I do whatever it says.
    – 7stud
    Apr 9, 2015 at 17:08
  • 1
    @DhruvGhulati Not that I know of, but you could inverse-grep it. Like tree | grep -v 'json' or similar.
    – slhck
    Aug 4, 2016 at 8:20
67

Not exactly the same, but one quick way on the Mac is:

find .

and that's it. It will list all file paths in the current directory as a list.

6
  • If interested in a particular directory: find ./<dir_name>
    – raspacorp
    Jul 8, 2016 at 22:12
  • 1
    If only interested in files, find . -type f
    – Jared Beck
    Mar 16, 2017 at 17:54
  • 6
    I was only interested in going a few levels deep to show the directory structure so find . -type d -maxdepth 2 worked for me Apr 21, 2017 at 7:13
  • Better use find * if you don't want to see hidden files and directories. tree doesn't show hidden files and directories by default.
    – asmaier
    May 31, 2018 at 14:36
  • 1
    This will do nicely. Takes the wind out of the sails of those who answer each and every question "How do I install X on the Mac?" with "With homebrew, just say brew X". I never understood why anyone would like to maintain an extra package management system and be dependent on some people packaging things -- but to each his own. Mar 26, 2020 at 10:01
28

Or if your administrator won't let you install any of the brew, fink, port tools you can always build it from the source :

curl -O ftp://mama.indstate.edu/linux/tree/tree-1.5.3.tgz
tar xzvf tree-1.5.3.tgz
cd tree-1.5.3/
ls -al

Edit the Makefile to comment linux part and uncomment osx area:

# Linux defaults:
#CFLAGS=-ggdb -Wall -DLINUX -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
#CFLAGS=-O2 -Wall -fomit-frame-pointer -DLINUX -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
#LDFLAGS=-s

# Uncomment for OS X:
CC=cc
CFLAGS=-O2 -Wall -fomit-frame-pointer -no-cpp-precomp
LDFLAGS=
XOBJS=strverscmp.o

Optional: Forcing color output

And while you're at it, if you want to force tree to always colorize the output, you can always edit the main method of the tree.c file and add force_color=TRUE; before setLocale(LC_TYPE,"");

Finally hit make and you're done building tree for mac.

Tribute goes to Shaun Chapman for his original post on his blog.

8
  • Couldn't get to Shaun's website; thanks for the heads up on modifying the Makefile. Oct 26, 2012 at 14:27
  • Specifically, you can add force_color = TRUE;. No semicolon and you get a compile error.
    – tgrosinger
    Sep 6, 2013 at 15:55
  • 1
    I've just built version 1.7 and instead of setLocale its setlocale. So in tree.c, you look for setlocale(LC_TYPE,""); and thanks!
    – Avi Cohen
    May 2, 2014 at 9:49
  • Great answer. Can you add a note on how to move it to /bin (or some path so that it can be used globally)? Oct 10, 2014 at 4:57
  • 1
    FYI, compiling 1.7.0 under Yosemite I got: warning: format specifies type 'long' but the argument has type 'long long'. The fix was to change a format specifier from %9ld to %9lld. Jan 21, 2015 at 18:02
25

There isn’t a formal tree command per se however you can do this:

Save the following script to /usr/local/bin/tree

#!/bin/bash

SEDMAGIC='s;[^/]*/;|____;g;s;____|; |;g'

if [ "$#" -gt 0 ] ; then
   dirlist="$@"
else
   dirlist="."
fi

for x in $dirlist; do
     find "$x" -print | sed -e "$SEDMAGIC"
done

Change the permissions so you can run it:

chmod 755 /usr/local/bin/tree 

Of course you may have to create /usr/local/bin:

sudo mkdir -p /usr/local/bin/tree 
8
  • It misses all the options of tree, but still a nice little solution.
    – slhck
    Nov 21, 2011 at 11:01
  • @slhck hehe it was a quick hack solution... Nov 21, 2011 at 11:05
  • You might want to quote your variables though.
    – slhck
    Nov 21, 2011 at 11:12
  • 1
    @JenS. of course you can simply change the find command to deal with that Sep 24, 2014 at 20:41
  • 3
    I prefer this SED SEDMAGIC='s;[^/]*/;|-- ;g;s;-- |; |;g' which lines up the subdirectory pipe with the first letter of the parent directory and useds dashes instead of underscores.
    – Jason S
    May 24, 2020 at 13:36
21

You should probably be using homebrew. If you do:

brew install tree
0
17

I found a simple solution here: http://murphymac.com/tree-command-for-mac/

So adding the following to your .bashrc, .bash_profile or any other place will make it work:

alias tree="find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'"

Now adding a tree command will print like this:

# ~/my-html-app [13:03:45]$ tree
.
|____app.js
|____css
| |____main.css
| |____theme.css
|____index.html
7
  • 1
    Great little one-liner, but it works for the current directory only Mar 26, 2020 at 10:02
  • So what are you trying to achieve? Mar 26, 2020 at 14:09
  • Sometimes I would like to draw a tree of another directory. Like I am in my home dir and want to see what's in, say, /tmp. Please understand that I like the elegance of this one-liner and upvoted your answer, just wanted to point out that this is not a full substitute for the "real" tree command, in case someone were looking for that. Mar 26, 2020 at 17:04
  • 1
    @AndrásAszódi you could create a shell function `` tree () { find $1 -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g' } ``
    – Logu
    Feb 28 at 12:01
  • 1
    @Logu Impressive, thank you for the suggestion. You get a +1 from me for it. However, the "real" tree can of course do more (print directories only, filter results etc.) I strongly suspect replicating all those would need more than one line ;-) Feb 28 at 13:09
6

An alternative using find and awk:

#!/bin/bash
find . -print 2>/dev/null | awk '!/\.$/ { \
    for (i=1; i<NF; i++) { \
        printf("%4s", "|") \
    } \
    print "-- "$NF \
}' FS='/'
1
  • This lines up the subdirectory pipe with the first letter of the parent directory which I much prefer to the SED command repeated in multiple answers, which just indents the subdirectory two spaces, but gives four underlines before the parent directory. I also prefer the dashes.
    – Jason S
    May 24, 2020 at 13:28
3

It's not as pretty as gnu tree ... but it's real easy to alias in bash ... You can even add a little color by tacking the G option on to osx's ls color.

alias tree='find . -type d | ls -lARG'
3

Here is a Ruby script solution that produces a nice Unicode tree along with useful metadata.

#!/usr/bin/env ruby
def tree_hierarchy( root, &children )
  queue = [[root,"",true]]
  [].tap do |results|
    until queue.empty?
      item,indent,last = queue.pop
      kids = children[item]
      extra = indent.empty? ? '' : last ? '└╴' : '├╴'
      results << [ indent+extra, item ]
      results << [ indent, nil ] if last and kids.empty?
      indent += last ? '  ' : '│ '
      parts = kids.map{ |k| [k,indent,false] }.reverse
      parts.first[2] = true unless parts.empty?
      queue.concat parts
    end
  end
end
def tree(dir)
  cols = tree_hierarchy(File.expand_path(dir)) do |d|
    File.directory?(d) ? Dir.chdir(d){ Dir['*'].map(&File.method(:expand_path)) } : []
  end.map do |indent,path|
    if path
      file = File.basename(path) + File.directory?(path) ? '/' : ''
      meta = `ls -lhd "#{path}"`.split(/\s+/)
      [ [indent,file].join, meta[0], meta[4], "%s %-2s %s" % meta[5..7] ]
    else
      [indent]
    end
  end
  maxs = cols.first.zip(*(cols[1..-1])).map{ |c| c.compact.map(&:length).max }
  tmpl = maxs.map.with_index{ |n,i| "%#{'-' if cols[0][i][/^\D/]}#{n}s" }.join('  ')
  cols.map{ |a| a.length==1 ? a.first : tmpl % a }
end
puts tree(ARGV.first || ".") if __FILE__==$0

You could modify the meta = … line to extract different metadata to display, hand-picking out the split pieces on the next line. With a little more work you could pass arbitrary ls arguments to select the metadata to show.

Sample output (looks nicer in the OS X terminal than the font on Stack Overflow):

phrogz$ tree UCC_IVI/
UCC_IVI/                               drwxr-xr-x  510B  Nov 20 11:07
  ├╴docs/                              drwxr-xr-x  102B  Nov 20 19:21
  │ └╴CANMessages.txt                  -rwxr-xr-x  2.2K  Nov 20 19:21
  │ 
  ├╴effects/                           drwxr-xr-x  204B  Nov 19 17:19
  │ ├╴Depth Of Field HQ Blur.effect    -rwxr-xr-x  2.4K  Nov 19 17:19
  │ ├╴FXAA.effect                      -rwxr-xr-x  1.6K  Nov 17 15:38
  │ ├╴HDRBloomTonemap.effect           -rwxr-xr-x   11K  Nov 17 15:38
  │ └╴SMAA1X.effect                    -rwxr-xr-x  4.4K  Nov 19 17:19
  │ 
  ├╴fonts/                             drwxr-xr-x  136B  Nov 17 15:38
  │ ├╴Arimo-Regular.ttf                -rwxr-xr-x   43K  Nov 17 15:38
  │ └╴OFL.txt                          -rwxr-xr-x  4.3K  Nov 17 15:38
  │ 
  ├╴maps/                              drwxr-xr-x  238B  Nov 19 17:19
  │ ├╴alpha-maps/                      drwxr-xr-x  136B  Nov 17 15:38
  │ │ ├╴rounded-boxes-3.png            -rwxr-xr-x  3.6K  Nov 17 15:38
  │ │ └╴splatter-1.png                 -rwxr-xr-x   35K  Nov 17 15:38
  │ │ 
  │ ├╴effects/                         drwxr-xr-x  136B  Nov 19 17:19
  │ │ ├╴AreaTex-yflipped.dds           -rwxr-xr-x  175K  Nov 19 17:19
  │ │ └╴SearchTex-yflipped.png         -rwxr-xr-x  180B  Nov 19 17:19
  │ │ 
  │ ├╴IBL/                             drwxr-xr-x  136B  Nov 17 15:38
  │ │ ├╴028-hangar.hdr                 -rwxr-xr-x  1.5M  Nov 17 15:38
  │ │ └╴FieldAirport.hdr               -rwxr-xr-x  1.5M  Nov 17 15:38
  │ │ 
  │ ├╴icons/                           drwxr-xr-x  238B  Nov 19 17:19
  │ │ ├╴icon_climate.dds               -rwxr-xr-x  683K  Nov 19 17:19
  │ │ ├╴icon_music.dds                 -rwxr-xr-x  683K  Nov 19 17:19
  │ │ ├╴icon_navigation.dds            -rwxr-xr-x  683K  Nov 19 17:19
  │ │ ├╴icon_phone.dds                 -rwxr-xr-x  683K  Nov 19 17:19
  │ │ └╴icon_surroundView.dds          -rwxr-xr-x  683K  Nov 19 17:19
  │ │ 
  │ └╴materials/                       drwxr-xr-x  102B  Nov 19 17:19
  │   └╴spherical_checker.png          -rwxr-xr-x   11K  Nov 19 17:19
  │   
  ├╴materials/                         drwxr-xr-x  102B  Nov 19 17:19
  │ └╴thin_glass_refractive.material   -rwxr-xr-x  6.0K  Nov 19 17:19
  │ 
  ├╴models/                            drwxr-xr-x  136B  Nov 19 17:19
  │ ├╴BokehParticle/                   drwxr-xr-x  136B  Nov 19 17:19
  │ │ ├╴BokehParticle.import           -rwxr-xr-x  739B  Nov 19 17:19
  │ │ └╴meshes/                        drwxr-xr-x  102B  Nov 19 17:19
  │ │   └╴Mesh.mesh                    -rwxr-xr-x  1.1K  Nov 19 17:19
  │ │   
  │ └╴Glass_Button/                    drwxr-xr-x  136B  Nov 19 17:19
  │   ├╴Glass_Button.import            -rwxr-xr-x  1.2K  Nov 19 17:19
  │   └╴meshes/                        drwxr-xr-x  136B  Nov 19 17:19
  │     ├╴GlassButton.mesh             -rwxr-xr-x   44K  Nov 19 17:19
  │     └╴Icon.mesh                    -rwxr-xr-x  1.8K  Nov 19 17:19
  │     
  ├╴scripts/                           drwxr-xr-x  204B  Nov 19 17:19
  │ ├╴App.lua                          -rwxr-xr-x  764B  Nov 17 15:38
  │ ├╴CANSim.lua                       -rwxr-xr-x   29K  Nov 17 15:38
  │ ├╴ObjectWiggler.lua                -rwxr-xr-x  3.7K  Nov 19 17:19
  │ └╴PathWiggler.lua                  -rwxr-xr-x  2.9K  Nov 17 15:38
  │ 
  ├╴states/                            drwxr-xr-x  170B  Nov 19 18:45
  │ ├╴app-camera.scxml                 -rwxr-xr-x  2.4K  Nov 20 11:07
  │ ├╴app-navigation.scxml             -rwxr-xr-x  590B  Nov 19 18:32
  │ └╴logic.scxml                      -rwxr-xr-x  4.2K  Nov 19 18:59
  │ 
  ├╴tests/                             drwxr-xr-x  102B  Nov 17 15:38
  │ └╴interface-navigation.scxml-test  -rwxr-xr-x   83B  Nov 17 15:38
  │ 
  ├╴UCC_IVI.uia                        -rwxr-xr-x  630B  Nov 19 17:32
  ├╴UCC_IVI.uia-user                   -rwxr-xr-x  832B  Nov 20 17:22
  ├╴UCC_IVI.uip                        -rwxr-xr-x  1.5K  Nov 17 15:38
  └╴UCC_Menu.uip                       -rwxr-xr-x   33K  Nov 19 17:19
1
  • 2
    Thanks very much for the lightweight solution! On OSX 10.9.5 (running stock Ruby 2.0.0), due to a 'no implicit conversion of true into String (TypeError)' error, I had to change the sixth line of the #tree method to read file = File.basename(path) + (File.directory?(path) ? '/' : '')
    – joel.neely
    Jul 16, 2016 at 13:33
3

Adding a small point to @apuche's answer for OSX El Capitan rootless feature. make install fails since we are not allowed to write into /usr/bin directory.

vikas@MBP:~/Downloads/tree-1.7.0$ sudo make install
Password:
install -d /usr/bin
install: chmod 755 /usr/bin: Operation not permitted
install -d /usr/share/man/man1
if [ -e tree ]; then \
        install tree /usr/bin/tree; \
    fi
install: /usr/bin/tree: Operation not permitted
make: *** [install] Error 71
vikas@MBP:~/Downloads/tree-1.7.0$

To overcome this, just edit Makefile to have prefix = /usr/local

0
0
  1. Install Xcode

  2. Get Command Line Tools

xcode-select --install
  1. Install Homebrew
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  1. Install tree
brew install tree
0
0

Late to the game, but I had the same question. Due to workplace restrictions, I couldn't install a package from source or via a third-party package manager.

This is my implementation:

#!/usr/bin/env bash
#
# Faux tree command.
#
# Recursive directory/file listing of present working directory (PWD), 
# with summary count of directories and files.
# Argument: 
#   Directory of interest, 
#     e.g.: tree /Users/foo/foo_dir
# Output:
#   Recursive directory/file listing of named directory,
# 
# Argument:
#   Directory of interest, with user-defined level of resursive depth, 
#     e.g.: tree /System/Library 2  
# Output:
#   Recursive directory/file listing of named directory, stopping at 
#   user defined depth of recursion, e.g. 2 

tree () {
  [ -n "$2" ] && local depth="-maxdepth $2";
  find "${1:-.}" "${depth}" -print 2> /dev/null | sed -e 's; 
  [^/]*/;|____;g;s;____|; |;g'
}

Simply add the function to /Users/foo/.profile or .bash_profile, then refresh the profile with: source .profile or: source .bash_profile

2
  • I got [^/]*/;|____;g;s; ...": unterminated substitute pattern
    – phuclv
    Apr 28, 2021 at 7:33
  • tried your previous version and it kind of works. You must not have a new line after s;
    – phuclv
    Apr 28, 2021 at 7:37
0

Installing broot very interesting command. You will get the following:

  • Searchability within the opened directory while navigating.
  • Open your file or document with your default app. (similar to ranger) to exit use (:q)

Install on macOSX using HomeBrew

 brew install broot

enter image description here

0
0

Improvement over one of the previous answers

tree() {
  dir="${1:-./}"
  [ -d "$dir" ] && find "$dir" -print | sed -e 's;[^/]*/;|____;g' -e 's;____|; |;g'
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .