31

Create the following files in a directory.

$ touch .a .b a b A B 你好嗎

My default ls order ignores the presence of leading dots, intermingling them with the other files.

$ ls -Al
total 0
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 a
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 .a
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 A
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 b
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 .b
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 B
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:06 你好嗎

I can change LC_COLLATE to put the dotfiles first.

$ LC_COLLATE=C ls -Al
total 0
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 .a
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 .b
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 A
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 B
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 a
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:03 b
-rw-r--r-- 1 sparhawk sparhawk 0 Jun  8 17:06 你好嗎

Unfortunately this makes the sort order case-sensitive, i.e. A and B precede a and b. Is there a way to print dotfiles first while staying case-insensitive (A and a precede B and b)?

Edit: attempting to modify LC_COLLATE

None of the answers so far fully replicate the functionality of ls easily. Conceivably, I could wrap some of them in a function, but this would have to include some detailed code on (e.g.) how to work with no argument vs. supplying a directory as an argument. Or how to deal with an explicit -d flag.

Alternatively, I thought that maybe there could be a better LC_COLLATE to use. However, I can't seem to make that work. I'm currently using LC_COLLATE="en_AU.UTF-8". I checked /usr/share/i18n/locales/en_AU (although I'm not sure if this is the right file, as I can't see any reference to UTF-8); I found the following.

LC_COLLATE
copy "iso14651_t1"
END LC_COLLATE

/usr/share/i18n/locales/iso14651_t1 contains copy "iso14651_t1_common". Finally, /usr/share/i18n/locales/iso14651_t1_common contains

 <U002E> IGNORE;IGNORE;IGNORE;<U002E> # 47 .

I deleted this line, ran sudo locale-gen, and restarted my computer. Unfortunately, this changed nothing.

5 Answers 5

21

OP was very close with editing /usr/share/i18n/locales/iso14651_t1_common, but the trick is not to delete the line

<U002E> IGNORE;IGNORE;IGNORE;<U002E> # 47 .

but rather to modify it to

<U002E> <RES-1>;IGNORE;IGNORE;<U002E> # 47 .

Why this works

The IGNORE statements specify that the full stop (aka period, or character <U002E>) will be ignored when ordering words alphabetically. To make your dotfiles come first, change IGNORE to a collating symbol that comes before all other characters. Collating symbols are defined by lines like

collating-symbol <something-inside-angle-brackets>

and they are ordered by the appearance of the line

<something-inside-angle-brackets>

In my copy of iso14651_t1_common, the first-place collating symbol is <RES-1>, which appears on line 3458. If you file is different, use whichever collating symbol is ordered first.

Details about character ordering with LC_COLLATE

<U002E> has three IGNORE statements because letters can be compared multiple times in case of ties. To understand this, consider lowercase a and uppercase A (which are part of a group of characters that actually get compared four times):

<U0061> <a>;<BAS>;<MIN>;IGNORE # 198 a
<U0041> <a>;<BAS>;<CAP>;IGNORE # 517 A

Having multiple rounds of comparison allow files that start with "a" and "A" to be grouped together because both are compared as <a> during the first pass, with the next letter determining the ordering. If all of the following letters are the same (e.g. a.txt and A.txt), the third pass will put a.txt first because the collating symbol for lowercase letters <MIN> appears on line 3467, before the collating symbol for uppercase letters <CAP> (line 3488).

Implementing this change

If you want the period to come first every time a program orders letters using LC_COLLATE, you can modify iso14651_t1_common as described above and rebuild your locations file. But if you want to make this change only to ls and without root access, you can copy the original locale files to another directory before modifying them.

What I did

My default locale is en_US, so I copied en_US, iso14651_t1, and iso14651_t1_common to $HOME/path/to/new/locales. There I made the abovementioned change to iso14651_t1_common and renamed en_US to en_DOTFILE. Next I compiled the en_DOTFILE locale with

localedef -i en_DOTFILE -f UTF-8 -vc $HOME/path/to/new/locales/en_DOTFILE.UTF-8

To replace the default ls ordering, make a BASH script called ls:

#!/bin/bash
LOCPATH=$HOME/path/to/new/locales LANG=en_DOTFILE.UTF-8 ls "$@"

save it somewhere that appears before /usr/bin on your path, and make it executable with chmod +x ls.

3
  • 1
    of course, you'll have to add -a or -A to see your dotfiles, but unless you always want to see them, it makes sense to do that on the command line, not in your BASH script
    – beandip
    Apr 24, 2017 at 20:51
  • Brilliant! Thank you, this is perfect! I just modified the root-owned file, so I didn't test your script. However, I think you need to put double quotes around your $@.
    – Sparhawk
    Apr 26, 2017 at 3:52
  • good call - double quotes added
    – beandip
    Jun 11, 2017 at 21:26
11

You can use the shell's sort order instead (which may not involve the locale's collation order; bash, AT&T ksh, yash, tcsh and zsh give the expected results, mksh and dash don't. fish seems to give a case insensitive order but gives different results when there are non-ASCII characters):

ls -dUl -- .* *

This gives ls an explicit list of files (and directories) to list, and deactivates ls's sorting (-U, which is a GNU extension).

There are a few caveats, depending on the shell you're using.

  • With zsh, the default nomatch option will cause the command to fail if the directory doesn't contain both hidden and non-hidden files; you could disable nomatch to avoid that, but better would be to do set -o cshnullglob instead (and the command to fail only if none of the globs match like in (t)csh or early Unix shells).
  • With zsh, pdksh and its derivative and fish, .*'s expansion doesn't include . and .., so this matches ls -Al. With other shells . and .. are included so it matches ls -al. In the latter case you'd need to change the globbing patterns to exclude . and .. (ls -dUl -- ..?* .[!.]* *).
  • Except in fish, (t)csh or zsh, if any of the globbing patterns don't match anything, ls will produce an error message; you can avoid this either by setting the nullglob option (in bash or zsh at least), or by redirecting stderr to /dev/null (ls -dUl -- ..?* .[!.]* * 2>/dev/null). If you use nullglob, watch out for the potentially-surprising behaviour that causes (see Shell eating `?` characters). fish behaves like bash with nomatch except that when interactive, a warning message will be issued for each glob that has no match.

(With thanks to Stéphane Chazelas for all the feedback!)

10
  • Note that not all shells will sort the list using the locale's collation order. mksh and dash for instance won't sort case-insensitively. Jun 8, 2016 at 9:22
  • 1
    Note that -U (to mean unsorted) is a GNU extension. Some other ls implementations like FreeBSD have a -U but it's not for unsorted listing. Jun 8, 2016 at 9:22
  • With GNU ls, you need -- before .* as that implementation accepts options after arguments (unless POSIXLY_CORRECT is in the environment) Jun 8, 2016 at 9:22
  • Sneaky (+1)! However, I'm not sure how I'd use this easily in all cases, i.e. in an alias or function. For example, it would have to change if I want to specify a specific directory to ls as an argument.
    – Sparhawk
    Jun 8, 2016 at 10:13
  • 1
    @PeterCordes [!.] is correct. See pubs.opengroup.org/onlinepubs/9699919799/utilities/…. Some (most?) shells allow ^ as a synonym for ! in negated character class globs. In any case, I prefer .[!.] .??* * as being slightly more comprehensible than .[!.]* ..?* *
    – jrw32982
    Jun 15, 2016 at 0:06
4

You might simply use two separate ls commands:

$ ls -dl ..?* .[^.]* 2>/dev/null ; ls -dl *
-rw-r--r--. 1 sparhawk sparhawk 0  8 Jun  09:29 .a
-rw-r--r--. 1 sparhawk sparhawk 0  8 Jun  09:29 .b
-rw-r--r--. 1 sparhawk sparhawk 0  8 Jun  09:29 a
-rw-r--r--. 1 sparhawk sparhawk 0  8 Jun  09:29 A
-rw-r--r--. 1 sparhawk sparhawk 0  8 Jun  09:29 b
-rw-r--r--. 1 sparhawk sparhawk 0  8 Jun  09:29 B
-rw-r--r--. 1 sparhawk sparhawk 0  8 Jun  09:29 你好嗎

Unlike the other answers so far, this approach displays the dot files first avoiding the . and .. entries, then the remaining entries in ls alphabetical order.

@StephenKitt answer's might be improved though to achieve the same result:

$ ls -dUl ..?* .[^.]* * 2>/dev/null
1
  • +1 too, but as per StephenKitt's answer, I'm not sure how I'd use this easily in all cases, i.e. in an alias or function. For example, it would have to change if I want to specify a specific directory to ls as an argument. (FWIW I'm using zsh, but this is useful for bash-people, I suppose.)
    – Sparhawk
    Jun 8, 2016 at 10:17
2

This is what I found in the manual

ls -Alhv --group-directories-first --color=auto

Explanation from man page

-A : List all except . and ..
-l : use a long listing format
-h : print sizes like 1K 234M 2G etc
-v : natural sort of (version) numbers within text

Eg : Without -v

ls -Alh --group-directories-first --color=auto

total 76K
drwxr-xr-x 50 shubham shubham 4.0K Apr  4 16:47  .cache
drwxr-xr-x 71 shubham shubham 4.0K Apr  4 11:46  .config
drwxr-xr-x  2 shubham shubham 4.0K Feb 18 23:48  Desktop
drwxr-xr-x  8 shubham shubham 4.0K Apr  3 10:08  Documents
drwxr-xr-x  6 shubham shubham 4.0K Apr  4 17:10  Downloads
drwxr-xr-x  7 shubham shubham 4.0K Feb 23 21:20  .local
drwx------  5 shubham shubham 4.0K Mar 23 21:45  .mozilla
drwxr-xr-x  2 shubham shubham 4.0K Feb 18 23:43  Music
drwxr-xr-x 12 shubham shubham 4.0K Mar 12 09:45  Pictures
drwx------  3 shubham shubham 4.0K Mar 26 21:05  .pki
drwxr-xr-x 11 shubham shubham 4.0K Apr  4 11:45  Public
drwxr-xr-x  2 shubham shubham 4.0K Feb 21 17:22  Templates
drwxr-xr-x  5 shubham shubham 4.0K Mar 30 11:08  Videos
drwxr-xr-x  5 shubham shubham 4.0K Mar 31 16:11 'VirtualBox VMs'
-rw-r--r--  1 shubham shubham   46 Feb 19 08:24  .dmrc
lrwxrwxrwx  1 shubham shubham   55 Apr  4 11:46  .imwheelrc -> /home/shubham/Documents/dotfiles/qutebrowser/.imwheelrc

Now with -v option

ls -Alhv --group-directories-first --color=auto
total 76K
drwxr-xr-x 50 shubham shubham 4.0K Apr  4 16:47  .cache
drwxr-xr-x 71 shubham shubham 4.0K Apr  4 11:46  .config
drwxr-xr-x  7 shubham shubham 4.0K Feb 23 21:20  .local
drwx------  5 shubham shubham 4.0K Mar 23 21:45  .mozilla
drwx------  3 shubham shubham 4.0K Mar 26 21:05  .pki
drwxr-xr-x  2 shubham shubham 4.0K Feb 18 23:48  Desktop
drwxr-xr-x  8 shubham shubham 4.0K Apr  3 10:08  Documents
drwxr-xr-x  6 shubham shubham 4.0K Apr  4 17:10  Downloads
drwxr-xr-x  2 shubham shubham 4.0K Feb 18 23:43  Music
drwxr-xr-x 12 shubham shubham 4.0K Mar 12 09:45  Pictures
drwxr-xr-x 11 shubham shubham 4.0K Apr  4 11:45  Public
drwxr-xr-x  2 shubham shubham 4.0K Feb 21 17:22  Templates
drwxr-xr-x  5 shubham shubham 4.0K Mar 30 11:08  Videos
drwxr-xr-x  5 shubham shubham 4.0K Mar 31 16:11 'VirtualBox VMs'
-rw-r--r--  1 shubham shubham   46 Feb 19 08:24  .dmrc
lrwxrwxrwx  1 shubham shubham   55 Apr  4 11:46  .imwheelrc -> /home/shubham/Documents/dotfiles/qutebrowser/.imwheelrc
3
  • 1
    Didn't this leave some of the dotfiles sorted at the end?
    – Jeff Schaller
    Apr 6, 2021 at 13:05
  • Yes thats because I used --group-directories-first. So this group the directories. If you remove --group-directories-first then it'll list all the dotfiles first Apr 7, 2021 at 5:06
  • Since that's what this questions asks for (Is there a way to print dotfiles first while staying case-insensitive (A and a precede B and b)?), I would suggest showing that in your answer.
    – Jeff Schaller
    Apr 9, 2021 at 16:48
-2

You can play with ls command options. Try this:

# ls -laXr

Where:

-l     use a long listing format
-a, --all
              do not ignore entries starting with .
-X     sort alphabetically by entry extension
-r, --reverse
              reverse order while sorting
1
  • Sorry, that doesn't seem to do what I want. The -X flag sorts by the extension after the ., which is totally different. Also, the files are in reverse alphabetical order. Also, while the dotfiles are first for my example, it won't work in all cases (e.g. a.b c.d .a .c). Also, you've used -a instead of -A.
    – Sparhawk
    Jun 8, 2016 at 10:24

You must log in to answer this question.

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