42

How can I list files with a filename ending with last character and with .txt extension ?

I have tried ls *+([[:digit:]]).txt but this is true for abc12.txt and abc2.txt.

But I need to get only abc2.txt. How can I do that?

Is there any sort form of :digit: that will do this?

1
  • 2
    It should be noted pattern is only valid with extended globbing endabled: shopt -s extglob. Aug 4, 2012 at 17:04

3 Answers 3

49

How about:

ls -d -- *[!0-9][0-9].txt

The ! at the beginning of the group complements its meaning.

As noted in the comments, this is bash's doing, try e.g.:

printf "%s\n" *[!0-9][0-9].txt
1
  • 8
    Note that bash (i.e. ls) does not support regular expressions here. These are filename expressions (Globbing). You can see the difference already in your example: here, * is a wildcard, which is not there in regular expressions, where you would use .* to achieve the same. Regular expressions are a lot more powerful than globbing. Jun 5, 2019 at 9:20
28

The question asked for regular expressions. Bash, and thus ls, does not support regular expressions here. What it supports is filename expressions (Globbing), a form of wildcards. Regular expressions are a lot more powerful than that.

If you really want to use regular expressions, you can use find -regex like this:

find . -maxdepth 1 -regex '\./.*[^0-9][0-9]\.txt'

Find is recursive by default, but ls is not. To only find files in the current directory, you can disable recursion with -maxdepth 1. Find matches against the filename with path, that's why the filenames start with ./ if you run find in .. The regex starts with \./ to cope with that. Note that in a regex, . is a special character matching any character, but here we really want a dot, that's why we escape it with a backslash. We do the same for the dot in .txt, because the regex would otherwise also match Atxt. The digit classes are same as for globbing, just that you need ^ instead of ! to invert the character class.

If you want to get the output of ls, then you can use -exec ls like this:

 find . -maxdepth 1 -regex '\./.*[^0-9][0-9]\.txt' -exec ls -lah {} \;

find supports a couple of different regex flavors. You can specify a -regextype, for example:

find . -maxdepth 1 -regextype egrep -regex '\./.*[^0-9][0-9]\.txt'

For me, possible types are: ‘findutils-default’, ‘awk’, ‘egrep’, ‘ed’, ‘emacs’, ‘gnu-awk’, ‘grep’, ‘posix-awk’, ‘posix-basic’, ‘posix-egrep’, ‘posix-extended’, ‘posix-minimal-basic’, ‘sed’ You can run find -regextype help to find out what is supported on your system.

5
  • Is the portion '\./' really necessary? Seems to work similarly without that part.
    – Rajib
    Oct 25, 2020 at 7:47
  • 1
    @Rajib In the example regex given above, \./ is followed by .*, which matches any characters, i.e. also ./. This means in this exact case, it is not necessary. It may be necessary if another pattern is used. Let's say if you want to match all files starting with a, then the regex \./a.* would do, but a.* would not. Oct 29, 2020 at 15:44
  • Wouldn't -maxdepth 1 always restrict to this directory, i.e. ./ ?
    – Rajib
    Oct 31, 2020 at 17:10
  • 1
    @Rajib Yes. But the regex you pass to find -regex always needs to match the whole path. If you pass the regex a.*, it is interpreted like ^a.*$. As all paths here start with ./, the regex a.* can never match, not even to the file called a. Nov 1, 2020 at 19:59
  • Got it. I was missing the -regex bit. I was thinking in terms of globbing. Thanks.
    – Rajib
    Nov 2, 2020 at 16:44
0

With the ksh extended globs (or bash -O extglob or zsh -o kshglob) that you're already using, that would be:

ls -d -- ?(*[![:digit:]])[[:digit:]].txt

Or

ls -d -- !(*[[:digit:]])[[:digit:]].txt

If you want to match on a1.txt and 2.txt but not a12.txt nor 12.txt.

However note that in ksh and bash (unless you set the failglob option to get a behaviour similar to zsh's), if that pattern doesn't match any file, the pattern will be passed literally to ls, and if that (strangely named) file happens to exist, it will be listed by ls even though it doesn't match the pattern itself.

To include .2.txt, set the dotglob option in bash, add the (D) glob qualifier in zsh, or set FIGNORE to !(.|..) in ksh93.

You must log in to answer this question.

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