0

So I've been playing around with filesystem and wondered about listing the files in /etc that contains only upper-case letters in their names. I commanded

ls *[A-Z]* 

But the console shows the files containing lower chars too. I want to use only ls command. Is the console program locale dependent?

What is the underlying cause?

  • Do you want file names with no lower case letter (like ABC.TXT, 123.C++, .123) or files that has no other character than uppercase characters (like ABC but not ABC.TXT which contains a . which is not an uppercase letter. – Stéphane Chazelas Aug 29 '17 at 13:02
  • I want file names containing Upper case letters no matter where they occur :) – Akash_Triv3di Aug 29 '17 at 13:03
  • 1
    That doesn't really answer my question. Foo.txT also contains uppercase letters. – Stéphane Chazelas Aug 29 '17 at 13:13
  • If you complain about your shell showing "the files containing lower chars too", I assume it matters where they occur. For example, they should not occur before or after lowercase letters?! – Philippos Aug 29 '17 at 13:16
  • "Is the console program Locale dependent?" Yes, shells depend on your locale, which may specify a lexicographic ordering that intersperses upper- and lower-case letters, and that's the underlying cause of your problem. Use [[:upper:]] instead of [A-Z]. Based on that comment, I don't think you want to match just all-upper-case names, but if so you should combine [[:upper:]] with an extended glob. I suggest you edit for clarification. – Eliah Kagan Aug 29 '17 at 13:20
3

[A-Z] doesn't mean upper case. It means letters from A to Z, which may include lower-case letters. Usually you should use [[:upper:]] instead. (This works in Bash even without extglob.)

What characters [A-Z] matches depends on your locale.

You have clarified that you want to show all filenames that contain at least on upper-case character anywhere--not only filenames consisting entirely of upper case--but that when you use ls *[A-Z]*, you get some filenames that don't have any upper-case characters in them.

This happens when your locale's lexicographic ordering interperses upper- and lower-case letters (e.g., AaBbCcDd...). Although you can set another locale (e.g., LC_ALL=C), the best solution is usually to write a pattern that specifically matches upper-case letters.

Which characters are upper-case letters may also vary between locales, but presumably if something is an upper-case letter in your locale then you want to include it. So that's probably an advantage of [[:upper:]] rather than a disadvantage.

Use [[:upper:]] instead.

Most Bourne-style shells, such as Bash, support POSIX character classes in globs. This command will list entries in /etc whose names have at least one upper-case letter:

ls -d /etc/*[[:upper:]]*

Some of the entries you get may be directories. If you want to show their contents rather than just list the directories, then you can remove the -d flag. You may also want to put a -- flag before the pattern, in case you have entries in /etc that begin with -. You probably don't, though. (In a script, you will usually want to use -- here.)

You probably don't want dotfiles, but if you do...

This will not show entries that start with .. Usually you don't want to show them. If you do want them, most shells allow you to write a single glob that also matches them or to configure globbing to include them by default. The option to automatically include leading-. entries in Bash is dotglob and it can be enabled with shopt -s dotglob. For other shells see . Or you can simply write a second glob for them:

ls -d /etc/*[[:upper:]]* /etc/.*[[:upper:]]*

Most popular Bourne-style shells support brace expansions, so you can write this more compactly with less repetition:

ls -d /etc/{,.}*[[:upper:]]*

In most shells including Bash, when you write two separate globs, you'll get an error message when either one does not expand--because the default behavior in most shells is to pass it unexpanded. But ls will still show the entries that matched the other one. But as Stéphane Chazelas has pointed out, in some shells including the very popular Zsh, the whole command fails and ls is never run. If you're using the shell interactively this is not really harmful, because you can modify the command run it again, but such constructions are unsuitable for portable scripts. Bash will also behave this way if you set the failglob shell option.

You don't need extended globbing for that.

In Bash you do not need to have extended globbing enabled to use POSIX character classes in glob patterns. On my system with Bash 4.3.48:

ek@Io:~$ shopt extglob
extglob         off
ek@Io:~$ ls -d /etc/*[[:upper:]]*
/etc/ConsoleKit     /etc/LatexMk         /etc/ODBCDataSources  /etc/UPower
/etc/ImageMagick-6  /etc/NetworkManager  /etc/rcS.d            /etc/X11

But you do need it to match filenames of only upper-case letters.

What you do need extended globbing for is if you want to match filenames consisting only of upper-case letters. Then you would use +([[:upper:]]) or *([[:upper:]]), and those are extended globs.

If you're using Bash, see this article, this guide, 3.5.8.1 Pattern Matching in the GNU Bash manual for details. See also Stéphane Chazelas's answer.

  • 1
    Note that ls -d /etc/*[[:upper:]]* /etc/.*[[:upper:]]* will fail the command if either pattern fails to match in zsh, fish or bash -o failglob and if both fail in csh/tcsh ({,.} comes from csh btw) or pre-Bourne shells (but would otherwise not cause an error if only one of them fails there as it would then be removed). Also note that in other shells, if there's no file matching /etc/*[[:upper:]]* but there's a file called literally /etc/*[[:upper:]]* there, it will be listed (even though it doesn't contain any uppercase letter). – Stéphane Chazelas Aug 29 '17 at 13:47
  • @StéphaneChazelas Good point. I'm not too worried about listing /etc/*[[:upper:]]* since it's an ls command which the OP is presumably going to inspect themselves rather than automatically parsing, and if they've set bash -o failglob they likely know they've done so. But that some other shells--including the very popular zsh!--default to failglob-like behavior is a major limitation of including a glob that cannot expand and something I had failed to consider. (I've now edited to warn about this explicitly.) – Eliah Kagan Aug 29 '17 at 13:58
  • 1
    See also Why is nullglob not default? – Stéphane Chazelas Aug 29 '17 at 14:06
2

For file names consisting solely of uppercase letter.

(like FOO, ÉTÉ, ΛΈΞΗ; unlike FOO.BAR, ÉTÉ (where that É is written as E followed by a U+0301 combining acute accent¹))

With ksh or zsh -o kshglob -o nobareglobqual or bash -O extglob:

ls -d +([[:upper:]])

With zsh -o extendedglob (which you'd rather use than kshglob):

ls -d [[:upper:]]#

Or with GNU ls (assuming file names only contain valid characters):

ls --ignore='*[^[:upper:]]*'

Or with find instead of ls (which here just outputs its arguments, I'd expect you'd want to use options like -l for it to be useful):

find . ! -name . -prune -name '*' ! -name '*[^[:upper:]]*'

(the -name '*' is to filter out the filenames that contain invalid characters, which the next ! -name would not be able to filter out (with some find implementations at least))

for file names without lowercase letters

(but still allow non-letters like in ABC.TXT), with ksh:

(FIGNORE='@(.|..|*[[:lower:]]*)'; ls -d -- *)

With bash -O dotglob -O extglob or zsh -o kshglob -o dotglob -o nobareglobqual:

ls -d -- !(*[[:lower:]]*)

Or zsh -o extendedglob:

ls -d -- ^*[[:lower:]]*(D)

Or with GNU ls (assuming file names only contain valid characters):

ls -A --ignore='*[[:lower:]]*' --ignore='.*[[:lower:]]*'

(the fact that the extra --ignore='.*[[:lower:]]*' is needed seems like a bug to me)

With find:

find . ! -name . -prune -name '*' ! -name '*[[:lower:]]*'

(with some find implementations, does not include filenames with invalid characters even if none of the valid characters are lower case ones).

For file names with at least one uppercase letter:

(like Foo.bar, .Été.txt, unlike 123.6, foo.bar)

With zsh -o dotglob or bash -O dotglob (the dotglob being to include files whose name starts with .):

ls -d -- *[[:upper:]]*

With find:

find . ! -name . -prune -name '*[[:upper:]]*'

(with some find implementations, does not include filenames with invalid characters even if some of the valid characters are uppercase ones)


¹ To allow combining characters, with zsh -o pcrematch, you could use a perl-like regular expression making use of Unicode character properties:

ls -d -- *(e@'[[ $REPLY =~ "^(?>\p{Lu}\pM*)*$" ]]'@)
  • 1
    This answers all the questions the OP could be asking, for most of the popular shells they could be using! +1 – Eliah Kagan Aug 29 '17 at 13:26
0

Forgive me for the previous answer, I didn't have my coffee yet.

I am unsure about doing this with just ls. But, here's another grep that should do the trick:

ls | egrep ^[^a-z0-9]*$

0

The pattern of your command will match any filename with at least one uppercase letter: Foo or bAr will match, because the * can match everything before or after the uppercase letter. That's why you get also get files with also lowercase letters in the name.

If you want files with only uppercase letters, you need extglob in bash:

shopt -s extglob

and then do

ls *([A-Z])

You may also need to set your locale to C or use [:upper:] instead of A-Z

  • 1
    Wether [A-Z] includes only uppercase letters depends on the locale. On GNU systems, and with bash and in most non-C locales, [A-Z] would also include the b-z English letters in addition to other non-English letters. – Stéphane Chazelas Aug 29 '17 at 13:16
  • I'm not sure about "most", but I added it to the answer. Thank you. – Philippos Aug 29 '17 at 13:20
0

Why do you want to use only ls? You can simply use find instead:
find -regex './[A-Z]+'

EDIT:
According to man 7 glob:

Wildcard matching
A string is a wildcard pattern if it contains one of the characters '?', '*' or '['. Globbing is the operation that expands a wildcard pattern into the list of pathnames matching the pattern. Matching is defined by:

A '?' (not between brackets) matches any single character.

A '*' (not between brackets) matches any string, including the empty string.

Character classes

An expression "[...]" where the first character after the leading '[' is not an '!' matches a single character, namely any of the characters enclosed by the brackets.

Regular expressions
Note that wildcard patterns are not regular expressions, although they are a bit similar. First of all, they match filenames, rather than text, and secondly, the conventions are not the same: for example, in a regular expression '*' means zero or more copies of the preceding thing.

Now that regular expressions have bracket expressions where the negation is indicated by a '^', POSIX has declared the effect of a wildcard pattern "[^...]" to be undefined.

If you want to use ls you have to bear in mind that bash won't translate your '*' in the same way as find -regexp or grep. *[A-Z]* will try to match any string followed by uppercase letter followed by any string so basically any string.

  • just out of curiousity and because it is not working? – Akash_Triv3di Aug 29 '17 at 12:59
  • 2
    -regex or a find without file argument are GNU extensions. Also note that ./[A-Z]+ is also a glob for the shell which should be quoted. If you do touch ./A+, if will stop working. Like for shell globs, what [A-Z] matches depends on the locale and generally doesn't do what you want. – Stéphane Chazelas Aug 29 '17 at 13:28
0

When you use a filename globbing pattern on the command line, not just with ls, it's the shell that expands it to any matching names, not the command.

This means that ls *[A-Z]* isn't just using ls, it's also using the shell, which does the actual filename expansion.

To use only one command to find all the names in the current directory that consists solely of uppercase ASCII characters, you would have to resort to find (GNU find used here):

$ find . -maxdepth 1 -regex '^\./[A-Z]*$' -print

Change -print to -ls for an ls -l -like list.

There is no standard shell globbing pattern that may be used to find filenames with only uppercase characters, although bash (which I assume is what you're using) has extensions to these patterns to some that may, as you can see in some of the other answers. These extensions originally come from the AT&T ksh shell.

The pattern that you're using here, *[A-Z]*, will expand to all names consisting of at least one uppercase character.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.