I've run into a bit of a weird behaviour that I don't fully understand with ls
and Chinese filenames. I'm running macOS 13.6.1 with SIP enabled (no core OS modifications), MacPorts installed, and US English as the primary language.
First, run this little script in a blank folder to make some test files:
import random
random.seed(42)
for i in range(30):
n = random.randrange(3, 8)
fn = "".join(random.choice("一二三") for _ in range(n))
open(fn, "w")
This makes 30 files named with random combinations of the characters 一二三 (one, two, three).
Next, I run ls -l
on my Mac (version "macOS 13.5" according to the manpage):
% ls -l
total 8
-rw-r--r--@ 1 brx staff 164 Nov 25 02:41 test.py
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一一三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一一三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三三一三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三三三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三二三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三二二三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三一三一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三二一三二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三三二三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一二一一三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一二三二一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一一三三二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二三二三二三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二二一一二一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二二三二一二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二一一一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二一一一二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二三三三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一二一三二三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三一一二二二三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二三三二三二二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二三二一二二一
The files are clearly sorted by filename length, but are otherwise not sorted within identical lengths, as if ls
is treating all Chinese characters as being exactly equivalent.
LANG
is set to en_US.UTF-8
(and no LC_*
variable is set), so maybe this is just a problem with the English sorting?
% LANG=zh_CN.utf-8 ls -l
total 8
-rw-r--r--@ 1 brx staff 164 11 25 02:41 test.py
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一一三
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一三二
-rw-r--r--+ 1 brx staff 0 11 25 02:41 三三一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 二一二
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一一三一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一三二一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 三三一三
-rw-r--r--+ 1 brx staff 0 11 25 02:41 三三三三
-rw-r--r--+ 1 brx staff 0 11 25 02:41 三二三一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 三二二三
-rw-r--r--+ 1 brx staff 0 11 25 02:41 二一一一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 二一三三
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一三二三三
-rw-r--r--+ 1 brx staff 0 11 25 02:41 三一三一一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 三二一三二
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一三三二三一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一二一一三三
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一二三二一一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 二一一三三二
-rw-r--r--+ 1 brx staff 0 11 25 02:41 二三二三二三
-rw-r--r--+ 1 brx staff 0 11 25 02:41 二二一一二一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 二二三二一二
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一三二一一一一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一三二一一一二
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一三二三三三一
-rw-r--r--+ 1 brx staff 0 11 25 02:41 一二一三二三三
-rw-r--r--+ 1 brx staff 0 11 25 02:41 三一一二二二三
-rw-r--r--+ 1 brx staff 0 11 25 02:41 二三三二三二二
-rw-r--r--+ 1 brx staff 0 11 25 02:41 二三二一二二一
Right, maybe this is just the Mac built-in ls
being crappy; let's try GNU Coreutils (from MacPorts, ls (GNU coreutils) 9.4
):
% gls -l
total 4
-rw-r--r--+ 1 brx staff 164 Nov 25 02:41 test.py
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一一三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一一三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三三一三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三三三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三二三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三二二三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三一三一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三二一三二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三三二三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一二一一三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一二三二一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一一三三二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二三二三二三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二二一一二一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二二三二一二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二一一一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二一一一二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二三三三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一二一三二三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三一一二二二三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二三三二三二二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二三二一二二一
% LANG=zh_CN.utf-8 gls -l
总计 4
-rw-r--r--+ 1 brx staff 164 1125日 02:41 test.py
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一一三
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一三二
-rw-r--r--+ 1 brx staff 0 1125日 02:41 三三一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 二一二
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一一三一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一三二一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 三三一三
-rw-r--r--+ 1 brx staff 0 1125日 02:41 三三三三
-rw-r--r--+ 1 brx staff 0 1125日 02:41 三二三一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 三二二三
-rw-r--r--+ 1 brx staff 0 1125日 02:41 二一一一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 二一三三
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一三二三三
-rw-r--r--+ 1 brx staff 0 1125日 02:41 三一三一一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 三二一三二
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一三三二三一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一二一一三三
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一二三二一一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 二一一三三二
-rw-r--r--+ 1 brx staff 0 1125日 02:41 二三二三二三
-rw-r--r--+ 1 brx staff 0 1125日 02:41 二二一一二一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 二二三二一二
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一三二一一一一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一三二一一一二
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一三二三三三一
-rw-r--r--+ 1 brx staff 0 1125日 02:41 一二一三二三三
-rw-r--r--+ 1 brx staff 0 1125日 02:41 三一一二二二三
-rw-r--r--+ 1 brx staff 0 1125日 02:41 二三三二三二二
-rw-r--r--+ 1 brx staff 0 1125日 02:41 二三二一二二一
Besides the humourously broken date display from GNU Coreutils, nothing changes. The only thing that kinda seems to work is C.utf-8
:
% LANG=C.utf-8 ls -l
total 8
-rw-r--r--@ 1 brx staff 164 Nov 25 02:41 test.py
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ??????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ???????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ??????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ??????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ???????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ???????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ??????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ?????????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ??????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ??????????????????
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 ??????????????????
% LANG=C.utf-8 gls -l
total 4
-rw-r--r--+ 1 brx staff 164 Nov 25 02:41 test.py
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一一三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一一三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三三二三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二一一一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二一一一二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一三二三三三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一二一一三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一二一三二三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 一二三二一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三一一二二二三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三一三一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三三一三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三三三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三二一三二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三二三一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 三二二三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一一一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一一三三二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一三三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二一二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二三三二三二二
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二三二一二二一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二三二三二三
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二二一一二一
-rw-r--r--+ 1 brx staff 0 Nov 25 02:41 二二三二一二
What's going on here? Are locales broken on my Mac?
EDIT: To clarify the expected behaviour: I would expect ls
to sort the characters in any reasonable sorting order; some reasonable orders would be Unicode codepoint (一, 三, 二), numerical or stroke-count order (一, 二, 三), or Pinyin order (二, 三, 一, corresponding to "er, san, yi").
Some extra information to answer comments (in en_US.UTF-8
locale):
the order stays the same when piped to
sort
orgsort
with or without-u
.there is actually no C.utf-8 locale on my system which explains why I get the same output as in the C locale there, an order by byte value and each of the byte rendered as
?
.expr '一二三' '<' '三一二'
,expr '一二三' '>' '三一二'
,expr '一二三' = '三一二'
return 1, 0 and 0 respectively whether that's with macosexpr
or GNUexpr
.perl -MPOSIX -le 'print strcoll@ARGV' -- '一' '二'
outputs-140
whether that's with theperl
shipped with macos or MacPorts.perl -MPOSIX -le 'print strcoll@ARGV' -- '一一' '二'
outputs19968
the encodings of those characters look like
$'\344\270\200\344\270\200\344\270\211'
for一一三
as reported bygls
in theC
locale, so it looks like they are properly encoded in UTF-8.the output of
perl -MPOSIX -le 'print unpack "H*", strxfrm$_ for @ARGV' -- '一' '一一' '二'
is:303034323030303030346c32 30303432303034323030303030346c3230346c32 303034323030303030346e3e
the output of
perl -MPOSIX -le 'print "$_\t" . unpack "H*", strxfrm$_ for <*>'
is一一三一 303034323030343230303432303034323030303030346c3230346c3230346c3b30346c32 一一三 3030343230303432303034323030303030346c3230346c3230346c3b 一三三二三一 3030343230303432303034323030343230303432303034323030303030346c3230346c3b30346c3b30346e3e30346c3b30346c32 一三二一一一一 303034323030343230303432303034323030343230303432303034323030303030346c3230346c3b30346e3e30346c3230346c3230346c3230346c32 一三二一一一二 303034323030343230303432303034323030343230303432303034323030303030346c3230346c3b30346e3e30346c3230346c3230346c3230346e3e 一三二一 303034323030343230303432303034323030303030346c3230346c3b30346e3e30346c32 一三二三三三一 303034323030343230303432303034323030343230303432303034323030303030346c3230346c3b30346e3e30346c3b30346c3b30346c3b30346c32 一三二三三 30303432303034323030343230303432303034323030303030346c3230346c3b30346e3e30346c3b30346c3b 一三二 3030343230303432303034323030303030346c3230346c3b30346e3e 一二一一三三 3030343230303432303034323030343230303432303034323030303030346c3230346e3e30346c3230346c3230346c3b30346c3b 一二一三二三三 303034323030343230303432303034323030343230303432303034323030303030346c3230346e3e30346c3230346c3b30346e3e30346c3b30346c3b 一二三二一一 3030343230303432303034323030343230303432303034323030303030346c3230346e3e30346c3b30346e3e30346c3230346c32 三一一二二二三 303034323030343230303432303034323030343230303432303034323030303030346c3b30346c3230346c3230346e3e30346e3e30346e3e30346c3b 三一三一一 30303432303034323030343230303432303034323030303030346c3b30346c3230346c3b30346c3230346c32 三三一三 303034323030343230303432303034323030303030346c3b30346c3b30346c3230346c3b 三三一 3030343230303432303034323030303030346c3b30346c3b30346c32 三三三三 303034323030343230303432303034323030303030346c3b30346c3b30346c3b30346c3b 三二一三二 30303432303034323030343230303432303034323030303030346c3b30346e3e30346c3230346c3b30346e3e 三二三一 303034323030343230303432303034323030303030346c3b30346e3e30346c3b30346c32 三二二三 303034323030343230303432303034323030303030346c3b30346e3e30346e3e30346c3b 二一一一 303034323030343230303432303034323030303030346e3e30346c3230346c3230346c32 二一一三三二 3030343230303432303034323030343230303432303034323030303030346e3e30346c3230346c3230346c3b30346c3b30346e3e 二一三三 303034323030343230303432303034323030303030346e3e30346c3230346c3b30346c3b 二一二 3030343230303432303034323030303030346e3e30346c3230346e3e 二三三二三二二 303034323030343230303432303034323030343230303432303034323030303030346e3e30346c3b30346c3b30346e3e30346c3b30346e3e30346e3e 二三二一二二一 303034323030343230303432303034323030343230303432303034323030303030346e3e30346c3b30346e3e30346c3230346e3e30346e3e30346c32 二三二三二三 3030343230303432303034323030343230303432303034323030303030346e3e30346c3b30346e3e30346c3b30346e3e30346c3b 二二一一二一 3030343230303432303034323030343230303432303034323030303030346e3e30346e3e30346c3230346c3230346e3e30346c32 二二三二一二 3030343230303432303034323030343230303432303034323030303030346e3e30346e3e30346c3b30346e3e30346c3230346e3e test.py 303033563030333830303355303033563030314d303033523030335f30303030303033563030333830303355303033563030314d303033523030335f