Given a directory (such as C:/), given from stdin or read from a file, produce a directory tree, with each file/folder indented based on its depth.


If I have a C:/ drive which only contains two folders foo and bar, and bar is empty while foo contains baz.txt, then running with input C:/ produces:


while running with input C:/foo/ should produce


As this is codegolf, lowest byte count wins. The file extensions (such as baz.txt) are optional. Extra notes: hidden files can be ignored, directories must actually exist, it can be assumed that files do not contain unprintable characters or new lines but all other printable ASCII characters are fine (file names with spaces must be supported). Output can be written to file or stdout. The indentations can be made up of either a tab character or 4 spaces.

  • 1
    \$\begingroup\$ Extra note: this question is poorly formatted, so a reformatting would be appreciated. \$\endgroup\$
    – Mathime
    Jul 16, 2016 at 20:38
  • \$\begingroup\$ Are languages that do not have access to files automatically disqualified? \$\endgroup\$
    – Leaky Nun
    Jul 16, 2016 at 20:48
  • \$\begingroup\$ What filenames have to be supported? Files with spaces in their names? With newlines? With unprintable characters? What about hidden files (beginning with .)? \$\endgroup\$
    – Doorknob
    Jul 16, 2016 at 20:49
  • 1
    \$\begingroup\$ @LeakyNun Output of reference question is an array of arrays. This question requires a representation of the directory tree to be printed to stdout. \$\endgroup\$
    – Mathime
    Jul 16, 2016 at 20:56
  • 1
    \$\begingroup\$ Can the input be a string parameter to a function? \$\endgroup\$
    – mbomb007
    Jul 18, 2016 at 16:32

11 Answers 11


bash, 61 58 54 bytes

find "$1" -exec ls -Fd {} \;|perl -pe's|.*?/(?!$)|  |g'

Takes input as a command line argument, outputs on STDOUT.

Note that the spaces near the end before the |g are actually a tab character (SE converts them to spaces when displaying posts).

find              crawl directory tree recursively
"$1"              starting at the input directory
-exec             and for each file found, execute...
ls -Fd {} \;      append a trailing slash if it's a directory (using -F of ls)
|perl -pe         pipe each line to perl
s|                replace...
.*?/              each parent directory in the file's path...
(?!$)             that doesn't occur at the end of the string...
|    |            with a tab character...
g                 globally

Thanks to @Dennis for 4 bytes!


Dyalog APL, 48 bytes

(⊂∘⊃,1↓'[^\\]+\\'⎕R'    ')r[⍋↑r←⎕SH'dir/s/b ',⍞]

prompt for character input

'dir/s/b ', prepend text

⎕SH execute in shell

r← store in r

make list of strings into character matrix

indices for ascending sorting

r[...] reorder r [sorted]

(...) on the standard out of the shell command, do:

'[^\\]+\\'⎕R' ' regex replace backslash-terminated runs of non-backslashes by four spaces

1↓ drop the first line

⊂∘⊃, prepend the enclosed first [line]

Result of inputting "\tmp" to the prompt begins as follows on my computer:


  • \$\begingroup\$ Aren't the directories supposed to have trailing \ characters? \$\endgroup\$
    – Neil
    Jul 18, 2016 at 12:22
  • \$\begingroup\$ @Neil they are preferred but not mandatory \$\endgroup\$
    – Adám
    Jul 18, 2016 at 16:09

SML, 176 bytes

open OS.FileSys;val! =chDir;fun&n w=(print("\n"^w^n);!n;print"/";c(openDir(getDir()))(w^"\t");!"..")and c$w=case readDir$of SOME i=>(&i w handle _=>();c$w)|x=>()fun%p=(&p"";!p)

Declares (amongst others) a function % which takes a string as argument. Call with % "C:/Some/Path"; or % (getDir()); for the current directory.

I'm using the normally rather functionally used language StandardML whose FileSys-Library I discovered after reading this challenge.

The special characters !, &, $ and % have no special meaning in the language itself and are simply used as identifiers; however they can't be mixed with the standard alphanumeric identifiers which allows to get rid of quite some otherwise needed spaces.

open OS.FileSys;
val ! = chDir;                       define ! as short cut for chDir

fun & n w = (                        & is the function name
                                     n is the current file or directory name
                                     w is a string containing the tabs
    print ("\n"^w^n);                ^ concatenates strings
    ! n;                             change in the directory, this throws an 
                                     exception if n is a file name
    print "/";                       if we are here, n is a directory so print a /
    c (openDir(getDir())) (w^"\t");  call c with new directory and add a tab to w
                                     to print the contents of the directory n
    ! ".."                           we're finished with n so go up again
and c $ w =                          'and' instead of 'fun' must be used 
                                     because '&' and 'c' are mutual recursive
                                     $ is a stream of the directory content
    case readDir $ of                case distinction whether any files are left
        SOME i => (                  yes, i is the file or directory name
            & i w handle _ => ();    call & to print i an check whether it's a 
                                     directory or not, handle the thrown exception 
            c $ w )                  recursively call c to check for more files in $
        | x    => ()                 no more files, we are finished

fun % p = (                          % is the function name, 
                                     p is a string containing the path
    & p "";                          call & to print the directory specified by p
                                     and recursively it's sub-directories
    ! p                              change back to path p due to the ! ".." in &

Can be compiled like this with SML/NJ or with Moscow ML* by prefixing with load"OS";.

*See mosml.org, can't post more than 2 links.


C# (.NET Core), 222 bytes

namespace System.IO{class P{static int n;static void Main(String[]a){Console.WriteLine(new string('\t',n++)+Path.GetFileName(a[0]));try{foreach(var f in Directory.GetFileSystemEntries(a[0])){a[0]=f;Main(a);}}catch{}n--;}}}

Try it online!

The ungolf:

using System.IO;
using System;

class P
    static int n=0;
    static void Main(String[] a)
        for (int i=0;i<n;i++) Console.Write("\t");

            foreach (String f in Directory.GetFileSystemEntries(a[0]))
                Main(new String[]{f});

First time I ever recursed a Main function!

I believe a person that has fresher knowledge of C# can golf it more, as I didn't program on C# for some time!


PHP, 180 bytes

  • first argument: path must have a trailing slash (or backslash)
  • second argument: level defaults to NULL and will be interpreted as 0 by str_repeat; will throw a warning if not provided

function d($p,$e){$s=opendir($p);echo$b=str_repeat("\t",$e++),$e?basename($p)."/":$p,"
";while($f=readdir($s))echo preg_match("#^\.#",$f)?"":is_dir($p.$f)?d("$p$f/",$e):"$b\t$f
  • displays hidden files and directories, but does not recurse hidden directories
    add parentheses around is_dir(...)?d(...):"..." to remove hidden entries from output (+2)
    replace "#^\.#" with #^\.+$# to display/recurse hidden entries but skip dot entries (+2)
  • may throw errors when directories are nested too deep. Insert closedir($s); before the final } to fix (+13)
  • will fail if a directory contains an entry with no name, prepend false!== to the while condition to fix (+8)

with glob, 182 bytes (probably 163 in future php)

function g($p,$e){echo$b=str_repeat("\t",$e),$e++?basename($p)."/":$p,"
";foreach(glob(preg_replace("#[*?[]#","[$1]",$p)."*",2)as$f)echo is_dir($f)?g($f,$e):"$b\t".basename($f)."
  • does not display or recurse hidden files/directories
  • 2 stands for GLOB_MARK, will append a slash to all directory names, as does ls -F
  • the preg_replace escapes glob special characters
    I could have abused preg_quote for this (-19); but that would fail on Windows systems, as the backslash is the directory separator there.
  • php may soon include a function glob_quote, which will allow the same golfing as preg_quote and work on all systems.

with iterators, 183 bytes
(well, not purely iterators: I used implicit SplFileInfo::__toString() to golf $f->getBaseName() and $f->isDir() to the old PHP 4 functions.)

function i($p){echo"$p
";foreach($i=new RecursiveIteratorIterator(new RecursiveDirectoryIterator($p),1)as$f)echo str_repeat("\t",1+$i->getDepth()),basename($f),is_dir($f)?"/":"","
  • no trailing slash required
  • displays and recurses hidden entries (ls -a)
  • insert ,4096 or ,FilesystemIterator::SKIP_DOTS before ),1 to skip dot entries (+5) (ls -A)
  • flag 1 stands for RecursiveIteratorIterator::SELF_FIRST

PowerShell, 147 bytes

param($a)function z{param($n,$d)ls $n.fullname|%{$f=$_.mode[0]-ne"d";Write-Host(" "*$d*4)"$($_.name)$(("\")[$f])";If(!$f){z $_($d+1)}}}$a;z(gi $a)1

Man, I feel like PS should be able to do something like the bash answer, but I'm not coming up with anything shorter than what I've got here.


param($a)                     # assign first passed parameter to $a
function z{param($n,$d) ... } # declare function z with $n and $d as parameters
ls $n.fullname                # list out contents of directory
|%{ ... }                     # foreach
$f=$_.namde[0]-ne"d"          # if current item is a file, $f=true
Write-Host                    # writes output to the console
(" "*$d*4)                    # multiplies a space by the depth ($d) and 4
"$($_.name)$(("\")[$f])"      # item name + the trailing slash if it is a directory
;if(!$f){z $_($d+1)}          # if it is a directory, recursively call z
$a                            # write first directory to console
z(gi $a)1                     # call z with $a as a directoryinfo object and 1 as the starting depth

Python 2, 138 bytes

Modified from this SO answer. Those are tabs for indentation, not spaces. Input will be taken like "C:/".

import os
for r,d,f in os.walk(p):
    t=r.replace(p,'').count('/');print' '*t+os.path.basename(r)
    for i in f:print'   '*-~t+i

Try it online - It's pretty interesting that I'm allowed to browse the directory on Ideone...

Same length:

from os import*
for r,d,f in walk(p):
    t=r.replace(p,'').count(sep);print' '*t+path.basename(r)
    for i in f:print'   '*-~t+i

Batch, 237 bytes

@echo off
echo %~1\
for /f %%d in ('dir/s/b %1')do call:f %1 %%~ad "%%d"
set f=%~3
call set f=%%f:~1=%%
set i=
set i=\t%i%
set f=%f:*\=%
if not %f%==%f:*\=% goto l
set a=%2
if %a:~0,1%==d set f=%f%\
echo %i%%f%

Where \t represents the literal tab character. This version includes trailing \s on directories but 41 bytes can be saved if they are not needed.

  • \$\begingroup\$ the trailing `\` s aren't needed \$\endgroup\$
    – ASCII-only
    Jul 25, 2016 at 12:43

Perl, 89 bytes

It's useful when there's a find module in the core distribution. Perl's File::Find module does not traverse the tree in alphabetic order, but the spec didn't ask for that.

/usr/bin/perl -MFile::Find -nE 'chop;find{postprocess,sub{--$d},wanted,sub{say" "x$d.$_,-d$_&&++$d&&"/"}},$_'

The script proper is 76 bytes, I counted 13 bytes for the command-line options.


Tcl, 116 bytes

proc L f {puts [string repe \t [expr [incr ::n]-1]][file ta $f];lmap c [glob -n -d $f *] {L $c};incr ::n -1}
L $argv

Try it online!


Java 8, 205 bytes

import java.io.*;public interface M{static void p(File f,String p){System.out.println(p+f.getName());if(!f.isFile())for(File c:f.listFiles())p(c,p+"\t");}static void main(String[]a){p(new File(a[0]),"");}}

This is a full program submission which takes input from its first command-line argument (not explicitly permitted, but done by many others) and prints output to standard out.

Try It Online (note different interface name)


import java.io.*;

public interface M {
    static void p(File f, String p) {
        System.out.println(p + f.getName());
        if (!f.isFile())
            for (File c : f.listFiles())
                p(c, p + "\t");

    static void main(String[] a) {
        p(new File(a[0]), "");

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.