8

I would like to perform an accent-insensitive search for files in a directory hierarchy.

$ touch a ą ä à á â
$ find . -iname '*a*'
./a
# How do I get find to return all 6 filenames?

I'm running Debian 11, Bullseye

My collation-fu is very weak!

Is there an option, locale setting, or some other method that I could use to make find work in an accent-insensitive manner?

As requested in a comment, locale returns:

LANG=en_GB.UTF-8
LANGUAGE=en_GB:en
LC_CTYPE="en_GB.UTF-8"
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_PAPER="en_GB.UTF-8"
LC_NAME="en_GB.UTF-8"
LC_ADDRESS="en_GB.UTF-8"
LC_TELEPHONE="en_GB.UTF-8"
LC_MEASUREMENT="en_GB.UTF-8"
LC_IDENTIFICATION="en_GB.UTF-8"
LC_ALL=
4
  • What is your locale set to?
    – muru
    Jul 3, 2023 at 9:35
  • I'm able to match them with find . -iregex './[a-z]$', but I think this isn't precise enough for what you need
    – Panki
    Jul 3, 2023 at 9:42
  • 1
    These characters are from Latin-1 Supplement and Latin Extended-A The Perl regex "[aà-åĀ-ą]" can match them, but GNU Find has no support for it. fd does.
    – memchr
    Jul 3, 2023 at 10:44
  • I wonder how shells (esp. zsh)'s nocaseglob (or similar) options behave, because find -iname 'glacier' -exec ••• could very well be replaced with for file in **/(#i)glacier ; do ••• Jul 3, 2023 at 13:26

2 Answers 2

2

TL;DR Scroll to the end

That's a fantastic question. Thanks for asking it.

It is possible to perform an accent-insensitive search, but not by default, and not automatically, as far as I know. You can find all six of your sample files with this:

find . -name '[[=a=]]'

That's standard POSIX glob notation for all-characters-that-are-like-a-but-maybe-with-an-accent.

So, if you know all of the characters that might have an accented version, you can use the above notation explicitly in your searches. e.g.:

find . -name 'fran[[=c=]]ais' # To match a cedilla

But that's tedious and deeply unsatisfactory.

Note that the [[=a=]] notation can also be used with characters for which there aren't any accented versions. So [[=k=]] will match k.

So I recommend creating a script (accented) that takes a string on the command line, replaces every letter with the [[=x=]] version of it, and prints out the result, and then you can use that with find. e.g.:

#!/usr/bin/env perl
print join('', map { /\p{Letter}/ ? "[[=$_=]]" : $_ } split //, $ARGV[0]), "\n";

Using that with find might look like:

find . -name "`accented a`"

If you want it to feel automatic, and if you only use find in the most simple way, you could create a shell script (ffind) that combines find and accented:

#!/bin/sh
find "$1" -name "`accented \"$2\"`"

Then you could do:

ffind . a

But that would make it impossible to use find's other predicates.

When you need that, you would have to use the real find and accented explicitly (like above).

Here

A smarter solution is a wrapper around find (ffind) that scans for -name and -iname arguments, and effectively applies accented to the following argument, and then executes the resulting modified find command. e.g.:

#!/usr/bin/env perl
use warnings;
use strict;
# ffind - find wrapper that makes -name and -iname accent-insensitive
my @cmd;
while (@ARGV)
{
    # Gather command line arguments
    push @cmd, shift @ARGV;

    # Make -name and -iname arguments accent-insensitive
    if ($cmd[-1] =~ /^-i?name$/ && @ARGV)
    {
        push @cmd, join('', map { /\p{Letter}/ ? "[[=$_=]]" : $_ } split //, shift @ARGV);
    }
}
exec 'find', @cmd;

Then you can do to this to find all six of your sample files:

ffind . -name a

Of course, you could call it find and change the 'find' on the last line to '/usr/bin/find', and that would make find accent-insensitive transparently:

find . -name a

Sadly, this entire approach only works on some systems, like Debian 12, but not all. :-(

5
  • Note that the regular expression capabilities of GNU find depends on the system's regular expression library, not on GNU find. On some systems with limited support for equivalence classes, expressions using equivalence classes only match the given character ([[=a=]] matches a, but not ä, etc.), even if GNU find is used rather than any other implementation.
    – Kusalananda
    Aug 29, 2023 at 11:44
  • Good point. Note that the above isn't using regex (e.g. -regex -iregex). It's using globbing (i.e. -name and -iname). I don't know if that makes a difference to your point. No, the same applies. e.g. It works with Debian 12, but not macOS 10.14.
    – raf
    Aug 29, 2023 at 12:23
  • It changes things, but only that it then depends on the system's fnmatch() implementation. I think equivalence classes aren't even standard in filename patterns... Since the user says they are on Debian, maybe it's enough to qualify that part of the answer with "works on Debian" (which I personally can't test).
    – Kusalananda
    Aug 29, 2023 at 12:28
  • Now that I think about it, fnmatch isn't working but GNU find is on Debian 12, but not on macOS 10.14. There must be reasons. I've noted this above. Thanks.
    – raf
    Aug 29, 2023 at 12:34
  • @Kusalananda Equivalence classes are a POSIX fnmatch() feature. Glob pattern notation is defined in terms of POSIX regexes and POSIX regexes support equivalence classes. Aug 30, 2023 at 12:47
1

You could transform the names to a decomposed form, remove combining marks and check against that:

find . -print0 |
  perl -C -MUnicode::Normalize -MFile::Basename -0 -lne '
    $name = NFD(basename($_)) =~ s/\pM//r;
    print if $name =~ /a/' |
  xargs -r0 ls -ld --

You must log in to answer this question.

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