How to sort first directories then files etc… when using “ls” in Unix

96

40

I would like to use the ls command to first show directories and then files. I tried:

ls -la | sort -k 1

But I got a wrong order.

atricapilla

Posted 2010-02-16T13:13:29.423

Reputation: 1 077

it's because - comes before d when using sort – Nifle – 2010-02-16T13:32:18.457

1Useful question! Perhaps it's time to change the accepted answer to one that is simpler and doesn't break terminal colors? – ippi – 2018-07-10T05:40:05.230

1Try ls -lh --group-directories-first – Diogo – 2019-02-06T14:34:23.040

4Old time unix heads (the ones from the pre-GUI age) used to capitalize their folder names and make plain-file names uncapitalized to get that result automagically. – JRobert – 2012-06-18T17:43:30.320

Answers

30

The following command will list directories first, ordinary files second, and links third.

ls -la | grep "^d" && ls -la | grep "^-" && ls -la | grep "^l"

Also, it would make a great deal of sense to create an alias for this command to save keystrokes.

Edit:

If you want directories first, and then everything that is not a directory second, use this:

ls -la | grep "^d" && ls -la | grep -v "^d"

eleven81

Posted 2010-02-16T13:13:29.423

Reputation: 12 423

1This command doesn't list everything if there are, for example, sockets or FIFOs in the folder – Studer – 2010-02-16T14:01:26.063

1To make @FCTW's command an OSX alias, add this to your ~/.profile: alias la="ls -la | grep \"^d\" && ls -la | grep \"^-\" && ls -la | grep -E \"^d|^-\" -v | grep -v \"^total\"" – aliteralmind – 2015-05-20T14:00:45.950

@aliteralmind Wouldn't it just be easier to use single quotes so that you wouldn't have to escape the double-quotes? – FCTW – 2015-05-29T03:44:26.777

How can this be modified so one can write lsx ~/dir that lists contents of a directory that is not the working directory? – kleinfreund – 2016-08-04T13:08:20.157

This is a stupid solution. Please delete it! – The Quantum Physicist – 2017-02-11T08:01:25.687

2I've found a working shorter notation: ls -la|grep ^d;ls -la|grep -v ^d (quotes aren't required and replaced && with ;). Another option is to introduce a variable and then evaluate it: a="ls -la|grep ^d";eval $a;eval $a -v. Could be useful to avoid repetitions when much more options are specified to ls/grep. There's also that ls -la --group-directories-first option, however the shortest imo is ls -la|sort – Steven Pribilinskiy – 2017-03-23T08:40:39.853

Since the commands are coupled with && instead of ;, this won’t list contents of directories not containing directories. I posted an explained version as an answer for reference. – kleinfreund – 2017-07-04T07:45:09.097

I'm in partial agreement with @TheQuantumPhysicist. Given the existence of the GNU-specific --group-directories-first option and the triviality of installing the GNU flavour of ls under macOS, this is an appallingly fragile answer guaranteed to both break under common edge cases and fail to support arbitrary ls options and arguments (e.g., --color=auto). That said, this answer shouldn't be deleted; it should simply be ignored. Yet another quaint, overly antiquated relic of a thankfully bygone era! See this answer for a truly robust solution.

– Cecil Curry – 2018-09-29T01:57:20.887

1note that filenames & directory starting with uppercase will be list on top with these commands. – xaa – 2018-10-10T13:48:36.990

1That edited version should still work with other types. The first grep says everything that starts with a 'd', and the second says everything that doesn't start with a 'd'. Surely everything either starts with a d or doesn't, right? – Ry4an Brase – 2012-08-24T12:58:17.177

To make the first one show other files too: ls -la | grep "^d" && ls -la | grep "^-" && ls -la | grep -E "^d|^-" -v | grep -v "^total" – Mark – 2013-04-26T22:25:40.863

1@Mark -- Why not just do: ls -la | grep "^d" && ls -la | grep "^-" && ls -la | grep -v -E "^d|^-|^total"? – FCTW – 2013-08-19T03:22:54.993

To pipe &&-divided commands to less:

`cat <(ls -la | grep "^d") <(ls -la | grep -v "^d") | less -r`
 – andrybak  – 2014-06-02T13:57:01.673

215

I do so love *nix and love seeing the inventiveness that goes into some of these replies...

Mine's not nearly as fancy on GNU Linux :

alias ls='ls --color -h --group-directories-first'

Given that I'm more comfortable with my linux CLI apps, I tend to also update coreutils on OSX :

brew install coreutils
alias ls='/usr/local/bin/gls --color -h --group-directories-first'

jonathanserafini

Posted 2010-02-16T13:13:29.423

Reputation: 2 464

Why alias ls? Why are we aliasing an already-existing command, and isn't typing ls --group-directories-first easy enough? – Kevin – 2014-07-29T23:21:21.430

4Purpose of alias ls is to define the configuration elements I want to use 90% of the time. Less keystrokes to achieve the desired output. If you ever then want to have native ls output you can always /bin/ls. – jonathanserafini – 2014-08-15T20:36:54.627

4@MEM If you're using OS X and you enjoy using Terminal, you should brew install bash Then, this will work! :) – Andrew Ashbacher – 2015-01-30T03:14:21.423

1@AndrewAshbacher no it won't. One, OSX uses bash as default most of the time. Second, it has no builtin LS. Third, it is based on BSD, not GNU/Linux. The commands above are based on the GNU Coreutils LS. – cde – 2015-09-26T07:34:07.487

1Updated with the OSX variant with brewed coreutils – jonathanserafini – 2015-10-01T12:09:03.357

2it might not be as fancy, but I'd prefer a command line parameter to a fancy pipe-to-single-line-script every time. No need to use a bazooka to kill a fly. Thanks :-) – Tuncay Göncüoğlu – 2015-12-28T10:36:39.290

5My opinion: this should have been the accepted answer as it lists a command-line option to accomplish exactly the goal, instead of a convoluted set of grep's etc. – Ryan Griggs – 2016-04-05T18:23:33.460

+1 so glad I kept reading after the accepted answer – Chris – 2019-06-17T06:17:54.450

7unfortunately, this doesn't work on osx terminal, since -- option is not available. – MEM – 2012-05-20T13:30:59.603

7re love: Is that sarcasm? If so, I'd agree. – allyourcode – 2012-06-02T01:06:06.393

great :) didn't know about the "alias" command :) – Alex – 2012-10-13T14:55:17.653

3Re: Doesn't work on mac ... True enough, personally though I generally do a brew install coreutils to override the mac builtins for my local user. – jonathanserafini – 2014-04-06T15:17:12.157

18

For Mac users coreutils:

brew install coreutils

alias ls='ls --color -h --group-directories-first'

Assuming your system is ready to homebrew:

Vadym Tyemirov

Posted 2010-02-16T13:13:29.423

Reputation: 281

8gls rather. Right? – Paul Irish – 2015-06-09T17:58:52.340

1Yes, by default. You can also use coreutils by default with PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH". – benesch – 2015-07-08T18:48:50.710

7

There are certain things I want to see in a directory listing, and so far none of the answers here meets all of the requirements below. My requirements for a directory listing:

  1. Directories and files are sorted alphabetically
  2. Directories are listed first
  3. Symbolic links (symlinks) are sorted like files
  4. Sorting is case-insensitive
  5. Sorting ignores all leading non-alpha characters in a filename
  6. Listing includes the total count of directories (excluding ./ and ../), files, and links
  7. Listing includes the total size (disk usage) of directories and files
  8. The listing has to look the same on Linux and Windows (Git Bash shell) -- this was the hardest to get right since convenient options like --group-directories-first don't work in Git Bash for Windows

After much hacking about, I finally came up with a one-liner (albeit a very long line ;-)) that I'm satisfied with. I have assigned this to an alias named 'dir':

ls -dlF --color * .* | head -n2 && ls -AlF | LC_ALL=C grep "^d" | 
LC_ALL=C sort -k 9df && ls -AlF | LC_ALL=C grep "^[l-]" | 
LC_ALL=C sort -k 9df && echo -e `find -maxdepth 1 -type d ! -name . | 
wc -l` Dir\(s\) `du -hs | cut -f 1`\\t\\t`find -maxdepth 1 -type f | 
wc -l` File\(s\) `find -maxdepth 1 -type f -print0 | du -ch --files0-from=- | 
tail -n 1 | cut -f 1`\\t\\t`find -maxdepth 1 -type l | wc -l` Link\(s\)

To make things easier to manage, I came up with separate commands to output each segment of the directory listing to my liking, then assembled them together using the && operator.

  • ls -dlF --color * .* | head -n2 -- Extract ./ and ../. We don't want to pass these through sort because they are already in the correct order, and sorting them can result in ../ being listed first. The -d option is to get rid of the "total" line; I like to add -F to show the trailing slash for directories (it will also mark symlinks with "@" when you do a plain ls -F).

  • ls -AlF | LC_ALL=C grep "^d" | LC_ALL=C sort -k 9df -- Extract the directories and sort them by filename (9th column), ignoring both non-alpha/space characters (d option) and character case (f option). The ls -A option excludes ./ and ../ from the listing since we already extracted them in the previous step. I tend to prefix all grep and sort commands with the LC_ALL=C locale reset so that (1) the output is consistent across Unix shells, and (2) you can sometimes see faster performance since it no longer has the overhead of the heavy UTF-8 character set to deal with.

  • ls -AlF | LC_ALL=C grep "^[l-]" | LC_ALL=C sort -k 9df -- This is similar to the step above, but this time we are sorting files and symlinks.

  • find -maxdepth 1 -type d ! -name . | wc -l -- Get the number of directories, excluding ./ and ../.

  • find -maxdepth 1 -type f | wc -l -- Get the number of files.

  • find -maxdepth 1 -type l | wc -l -- Get the number of symlinks.

  • du -hs | cut -f 1 -- Extract the total size of all subdirectories in human-readable format.

  • find -maxdepth 1 -type f -print0 | du -ch --files0-from=- | tail -n 1 | cut -f 1 -- Extract the total size of all files in human-readable format.

Let's see our new dir alias in action!

BEFORE:

$ ls -alF
total 22
drwxr-xr-x   13 Tom      Administ     4096 Oct 25 02:38 ./
drwxr-xr-x    3 Tom      Administ        0 Dec 24  2014 ../
drwxr-xr-x   15 Tom      Administ     4096 Sep 17 01:23 .VirtualBox/
-rw-r--r--    1 Tom      Administ      615 Oct 25 02:38 .aliases
-rw-r--r--    1 Tom      Administ    12742 Oct 24 11:47 .bash_history
-rw-r--r--    1 Tom      Administ     3234 Oct 24 15:06 .bash_profile
drwxr-xr-x    1 Tom      Administ        0 Jan 24  2015 .gem/
-rw-r--r--    1 Tom      Administ      586 Oct 24 03:53 .gitconfig
drwxr-xr-x    1 Tom      Administ     4096 Dec 28  2014 .ssh/
drwxr-xr-x    4 Tom      Administ        0 Jan 24  2015 .travis/
-rw-r--r--    1 Tom      Administ     6645 Oct 25 02:38 _viminfo
-rw-r--r--    1 Tom      Administ     4907 Oct 24 15:16 profile
drwxr-xr-x    1 Tom      Administ        0 Oct 24 22:20 tmp/

AFTER:

$ dir
drwxr-xr-x   13 Tom      Administ     4096 Oct 25 02:38 ./
drwxr-xr-x    3 Tom      Administ        0 Dec 24  2014 ../
drwxr-xr-x    1 Tom      Administ        0 Jan 24  2015 .gem/
drwxr-xr-x    1 Tom      Administ     4096 Dec 28  2014 .ssh/
drwxr-xr-x    1 Tom      Administ        0 Oct 24 22:20 tmp/
drwxr-xr-x    4 Tom      Administ        0 Jan 24  2015 .travis/
drwxr-xr-x   15 Tom      Administ     4096 Sep 17 01:23 .VirtualBox/
-rw-r--r--    1 Tom      Administ      615 Oct 25 02:38 .aliases
-rw-r--r--    1 Tom      Administ    12742 Oct 24 11:47 .bash_history
-rw-r--r--    1 Tom      Administ     3234 Oct 24 15:06 .bash_profile
-rw-r--r--    1 Tom      Administ      586 Oct 24 03:53 .gitconfig
-rw-r--r--    1 Tom      Administ     4907 Oct 24 15:16 profile
-rw-r--r--    1 Tom      Administ     6645 Oct 25 02:38 _viminfo
      5 Dir(s) 2.8M           6 File(s) 31K           0 Link(s)

One minor downside is that you cannot have colored listings, since the color control characters surrounding the filenames make the sorting too unreliable.


UPDATE

The alias above was painfully slow when executed from the root directory of a deep file system, so I have updated to this simpler but much more performant command:

ls -AFoqv --color --group-directories-first | tail -n +2 && find -maxdepth 1 -type f -printf '%s\n' | awk '{total+=$1} END {print total" bytes"}'

Sample output:

$ dir
drwxr-xr-x 1 Tom     0 Mar 29 13:49 .aws/
drwxr-xr-x 1 Tom     0 Mar 29 13:49 .gem/
drwxr-xr-x 1 Tom     0 Mar 29 19:32 .ssh/
drwxr-xr-x 1 Tom     0 Mar 29 13:49 .zbstudio/
drwxr-xr-x 1 Tom     0 Jun 16  2016 temp/
drwxr-xr-x 1 Tom     0 Jul 13  2016 vimfiles/
-rw-r--r-- 2 Tom   365 Mar 30 10:37 .aliases
-rw-r--r-- 1 Tom 16028 Mar 30 12:12 .bash_history
-rw-r--r-- 2 Tom  2807 Mar 30 12:12 .bash_profile
-rw-r--r-- 2 Tom  2177 Mar 29 23:24 .functions
-rw-r--r-- 1 Tom  1091 Mar 30 10:34 .gitconfig
-rw-r--r-- 1 Tom  8907 Mar 29 14:45 _viminfo
-rw-r--r-- 1 Tom  2444 Jul 13  2016 _vimrc
33819 bytes

Since the new version of Git Bash for Windows supports --group-directories-first, we no longer have to fall back on sort. Even though the new alias doesn't display as much information as the previous alias, the performance gains are more than worth it. As a perk, you also get colors!

thdoan

Posted 2010-02-16T13:13:29.423

Reputation: 641

1Great answer! But when you say, “After much hacking about, I finally came up with a one-liner…” Yes, indeed that is not really a “one liner” in the classic sense. It might make sense to take that logic and make it a standalone shell script and just run that or call it as an alias. – JakeGould – 2015-10-24T20:11:07.893

@JakeGould You are fast! I haven't even finished proof-reading my answer yet and you already finished reading it and shot off a comment :). Yeah, it's a prime candidate for a function, but I was too lazy at the time, so just threw it into my .bash_aliases file. On the other hand, I usually only write a function when I cannot do something without passing parameters. – thdoan – 2015-10-24T20:20:35.230

@10basetom: Quick question: How do you get the different colors for the columns in ls? I can color file names by filetype using ls --color, but I don't see a way to get those helpful colors for the columns. What's the secret? – Pirx – 2018-02-13T23:24:52.070

@Pirx I believe the columns are colored by the default Stack Exchange syntax highlighting :-). – thdoan – 2018-03-02T16:49:08.260

Yep, that seems to be the case, but it's a great idea, and solutions are in ;-) – Pirx – 2018-03-03T17:39:24.650

@Pirx I suppose you can convert this into a function and try one of the answers in https://stackoverflow.com/questions/20151601/color-escape-codes-in-pretty-printed-columns

– thdoan – 2018-03-05T07:25:00.013

6

You've got several choices, depending if you want to keep alphabetical order.

You could simply try :

ls -al | sort -k1 -r

or this, to keep alphabetic order for files with the same permissions :

ls -al | sort -k1,1 -k9,9 -r

or, as eleven81 said (but this version lists everything) :

ls -la | grep "^d" && ls -la | grep "^-" && ls -al | grep -v "^[d|-]"

Studer

Posted 2010-02-16T13:13:29.423

Reputation: 3 448

1This is a nicer solution IMO. Using several processes and pipes to do sorting instead of piping to sort seems kind of backwards. Especially since ls- al|sort -k1 -r works. What was missing is just the -r flag. – brice – 2010-02-17T13:54:37.677

@brice The thing that bothers me about 'sort' is that ./ and ../ won't be the first two lines, in that order. Other than that, I agree that it's the more efficient solution. – thdoan – 2015-10-23T21:41:59.753

How to color the output? – Danijel – 2019-09-05T06:54:59.273

5

To delerious010's answer, I would add that if you want old-style ordering:

LANG=C ls -la --group-directories-first

(or use LC_ALL or LANGUAGE or LC_COLLATE set to "C").

This will give something similar to:

.
..
DIR
Dir
dir
.hidden
123
UC_FILE
Uc_file
lc_file

Although, if I recall correctly, the hidden dot files originally appeared before the directories.

Paused until further notice.

Posted 2010-02-16T13:13:29.423

Reputation: 86 075

2

ls -laX will show you directories first in alphabetical order, but will screw the file list.

Long options:

ls
    -l    # List
    --all
    -X    # Sort alphabetically by entry extension

Bobby

Posted 2010-02-16T13:13:29.423

Reputation: 8 534

This would only work if you could be absolutely certain that every directory had no dots in its name. – eleven81 – 2010-02-16T13:43:36.727

2

Here's a function to do this (bash or zsh): And... I'm not suggesting this is the best way, but it's the one I came up with and am using right now:

function lss
{
    # Shows directory listing with directories at the top.

    command ls  --color=always $@ | egrep '^d|total'
    command ls  --color=always $@ | egrep -v '^d|total';
}

SuperMagic

Posted 2010-02-16T13:13:29.423

Reputation: 935

Just an fyi this doesn't appear to work. You need to use long listing format (ls -l) in order to filter by file type like that. Also, this command will break on spaces (in bash). You need to quote like so: "$@" If you want to use this approach, you could do something like so: function lss {local temp="$(command ls -l --color=always "$@")"; egrep --color=never '^d|total' <<<"$temp"; egrep --color=never -v '^d|total' <<<"$temp"} – Six – 2015-07-24T00:29:26.433

1

TL;DR

alias ls='ls -lhF --color'

list_sorted() {
    ls $* | grep "^d";
    ls $* | grep "^-";
    ls $* | grep -v -E "^d|^-|^total"
}

alias ll=list_sorted

Explanation

I use a combination of the solutions provided in the answers and comments here.

Default ls

First of all, I overwrite the default behavior for ls:

  • -l: Always display the list as a one-dimensional, vertical list
  • -h: Display file sizes in a human-readable fashion (e.g. 4.0K instead of 4096)
  • -F: Display indicators like a trailing slash for directories
alias ls='ls -lhF --color'

Extended ll

Next, I write a function containing the sorting logic. For each ls I pass any originally passed arguments to it. That enables me using the alias from a different working directory than the one I want to list (i.e. ls -a ~).

Also, each call to ls is piped to a grep command. Here, the sorting happens. ls -l | grep "^d" for example only lists directories. If directories should be listed first, this needs to come first in the function as well. Next thing is files.

Lastly, I show everything that is neither a directory nor a file (nor the line showing total size of the directory contents). This is done by grepping directoy, regular file entries and the total entry and then inverting the result via the -v argument.

list_sorted() {
    # List directories
    ls $* | grep "^d";
    # List regular files
    ls $* | grep "^-";
    # List everything else (e.g. symbolic links)
    ls $* | grep -v -E "^d|^-|^total"
}

Finally, I alias the function to a new command. In particular I don’t want to overwrite ls in case my function breaking in some scenarios. Then I want to be able to use ls. Alternatively, you can always invoke the un-aliased ls command by invoking \ls.

alias ll=list_sorted

Notes

  • I use ; instead of && as a delimiter for the commands. Otherwise, one is unable to list contents of directories not containing directories (the first ls command evaluates to false, thus not allowing the execution of the next command since it’s coupled with &&. ; avoids that.)

kleinfreund

Posted 2010-02-16T13:13:29.423

Reputation: 295

1

Another way ...

find . -d 1 -type d | ls -la | sort -r 

OR

ls -la | sort -r

OR

d=`find . -type d -d 1`;f=`find . -type f -d 1`; echo -e -DIRS- "\n$d\n" -FILES- "\n$f"

Eddie B

Posted 2010-02-16T13:13:29.423

Reputation: 909

What do these do? Where did you learn to type them? – Tamara Wijsman – 2012-06-11T18:31:33.913

Well My preferred OS is Debian Linux. Debian's gnu core utils package version of ls supports the --show-directories-first option ... when I started using OSX I simply grabbed my all my bash dot files from my Debian box and dropped them in my home directory ... I had a lot of bash_aliases that broke so ... It was then that I had to figure out some work-arounds for my aliases ... – Eddie B – 2012-06-12T01:13:19.727

The first one is a bit redundant ... There's really no reason to sort for the directories as it's the order we need to modify not directories ... the 'ls -la | sort -r' is what really works. Basically it's stating ...

  1. A) Find . -d 1 -type d (Start from this directory, search one directory depth and search for directories only)

    B) ls -la (list files all attributes)

    C) Sort them in reverse

  2. Do as 1) just drop the find ... it's not needed ... I actually like Studers' solution better ... :-)

  3. < – Eddie B – 2012-06-12T01:21:44.610

0

"Do one thing and do it well"

Solve for each part separately, then combine at the end. Consider choosing this answer bc solving for each sub command helps you understand each part of the process, making you a better developer, this is in line with the unix/nix philosophy:

List of directories sorted alphabetically

ls -lp | grep "/"

List of directories sorted alphabetically showing hidden folders

ls -lap | grep "/"

List of files sorted alphabetically

ls -lp | grep -v "/"

List of files sorted alphabetically showing hidden folders

ls -lap | grep -v "/"

First List of directories then files sorted alphabetically

ls -lp | grep "/" && ls -lap | grep -v "/"

First List of directories then files sorted alphabetically showing hidden

ls -lap | grep "/" && ls -lap | grep -v "/"

And of course you can create an alias to do this later if you wish

alias foldersThenFiles='ls -lap | grep "/" && ls -lap | grep -v "/"'

jasonleonhard

Posted 2010-02-16T13:13:29.423

Reputation: 117

You might also consider adding these aliases – jasonleonhard – 2020-02-19T16:53:05.773

alias folders='ls -lap | grep "/"' – jasonleonhard – 2020-02-19T16:53:40.993

alias files='ls -lap | grep -v "/"' – jasonleonhard – 2020-02-19T16:53:44.520

0

This is a script solution. Lists just the names, no inode data, alphabetical, not case sensitive, formatted into columns. Although it's row-major instead of column major like the default output of ls. The columns get a little messy if there is a file name with >26 characters.

rm -f /tmp/lsout
ls -1p | grep / | sort -f >> /tmp/lsout
ls -1p | grep -v / | sort -f >> /tmp/lsout

IFS=$'\n' read -d '' -r -a lines < /tmp/lsout

printf "%-24s  %-24s  %-24s\n" "${lines[@]}"

And another, with some extra formatting.

rm -f /tmp/lsout
echo "  ---- Directories ---- " >> /tmp/lsout
ls -1p | grep / | sort -f >> /tmp/lsout
IFS=$'\n' read -d '' -r -a lines < /tmp/lsout
printf "%-24s  %-24s  %-24s\n" "${lines[@]}"

rm -f /tmp/lsout
echo "  ------- Files ------- " >> /tmp/lsout
ls -1p | grep -v / | sort -f >> /tmp/lsout
IFS=$'\n' read -d '' -r -a lines < /tmp/lsout
printf "%-24s  %-24s  %-24s\n" "${lines[@]}"

Output for the last one looks like the following, minus the colors:

  ---- Directories ----   archive/                  bookmarks/              
Desktop/                  Documents/                Downloads/              
fff/                      health/                   Library/                
Movies/                   Music/                    Pictures/               
Public/                   rrf/                      scifi/            
testdir/                  testdir2/                                         
  ------- Files -------   @todo                     comedy            
delme                     lll                       maxims                  
schedule                  vtokens style

Just remember not to alias or change the default behavior of ls since this script calls it.

Igorio

Posted 2010-02-16T13:13:29.423

Reputation: 688