How would I pipe the ls through grep to have an alternating character case, as output?

For example:

I have files (1) aAbBaBbA, (2) bAbBaA, (3) bbAb,(4) AAaBbAa,(5) BBBaaa, and (6) aBaB.

I want to find 1,2,6.

What command would I type?

(Edit): My example wasn't as specific as it should have been. I wanted to include alternating starting with lower and upper. (6) should be changed from aBaB to BaBa, but I want to keep the original.

4 Answers 4



ls -1 | grep -E '^[A-Z]?([a-z][A-Z])*[a-z]?$'

EDIT: as pointed very correctly by @mikeserv, this won't work for non-ASCII chars. And indeed they do happen quite often (e.g. music files with foreign titles for filename). So the more robust way is:

ls -1 | grep -E '^[[:upper:]]?([[:lower:]][[:upper:]])*[[:lower:]]?$'

In the following, I retain just [A-Z] for sake of readability.

Also, a caveat: this will match a single char (upper or lower). One could argue that "alternating case" is defined by no successive similar case in a sequence of zero or more chars... :-)


mkdir -p /tmp/junk
cd /tmp/junk
touch aAbBaBbA bAbBaA bbAb AAaBbAa BBBaaa aBaB

ls -1 | grep -E '^[A-Z]?([a-z][A-Z])*[a-z]?$'
# aAbBaBbA
# aBaB
# bAbBaA

But that's not enough. Some more tests:

touch aB
touch aBcD
touch aBcDeF
touch aBcDEf
touch Ab
touch AbCd
touch AbCdEf
touch AbCdeF
touch AbCdEF
ls -1 | grep -E '^[A-Z]?([a-z][A-Z])*[a-z]?$'
# aAbBaBbA
# aB
# Ab
# aBaB
# aBcD
# AbCd
# aBcDeF
# AbCdEf
# bAbBaA
  • This matches blank lines.
    – mikeserv
    Jan 27, 2015 at 6:46
  • @mikeserv I don't often have filenames that are blank...
    – Pierre D
    Jan 27, 2015 at 6:46
  • mmh, on the other hand it will match single character either upper or lower. My bad.
    – Pierre D
    Jan 27, 2015 at 6:49
  • I'm not convinced a single char should be a failing case though - it is still alternating case, I think. There just isn't enough of it to fill out the pattern entirely... I guess @NoTime will probably chime in tomorrow.
    – mikeserv
    Jan 27, 2015 at 6:53
  • 1
    Yes - it is definitely what mine should have been - excepting that the robust way is to use [[:upper:]][[:lower:]] to avoid locale problems. Use a + rather than the Kleene * and you avoid the blank line thing.
    – mikeserv
    Jan 27, 2015 at 6:55

Type this command:

ls |grep -E '^([[:upper:]][[:lower:]])+[[:upper:]]?$|^([[:lower:]][[:upper:]])+[[:lower:]]?$'

To break this down.

  • ls is the main command

  • It is piped to grep

  • The switch -P or -E are telling grep to search using regular expressions (regex)

  • Within the single quote ' and the parentheses () the pattern is layed out.

  • Basically it reads match any upper letter [[:upper:]] then any smaller letter [[:lower:]]

  • Or using the pipe |

  • Then match any smaller letter [[:lower:]] then match any upper letter [[:upper:]]

  • The + will match everything within the parentheses () and play it out through the list given.

  • $ tells when the pattern ends

  • Have you tried that? Both commands match (6) only. Jan 27, 2015 at 4:24
  • @HaukeLaging yes I just spent most of the day doing it. My answer worked for me.
    – No Time
    Jan 27, 2015 at 4:36
  • What grep are you using? Mine is GNU grep 2.20 Jan 27, 2015 at 4:40
  • @HaukeLaging Version 2.21 imgur.com/0B3AquK is results. I also matched it online at regexr.com
    – No Time
    Jan 27, 2015 at 4:42
set aAbBaBbA bAbBaA bbAb AAaBbAa BBBaaa aBaB
l= u=;  printf %s\\n    "$@" |
        grep -E "^([${l:=[:lower:]}][${u:=[:upper:]}])+[$l]?$|^([$u][$l])+[$u]?$"



But see Pierre's answer for the (obviously) better version of this.

Still, there is another way:

grep -vE '[^[:alpha:]]|[[:lower:]]{2}|[[:upper:]]{2}|^$'

...though that will match only a single char on a line. You can fix that by doing a .? between the ^ and $ at the end though.

  • was hoping to do this without a script :) otherwise I would have checked with python.. but I think I could use the regex you have in there
    – No Time
    Jan 27, 2015 at 4:48
  • @NoTime - You can do it without a script - the for loop is just to get it all over to grep but actually, that's not the best way to do it....
    – mikeserv
    Jan 27, 2015 at 4:49
  • I changed it to full command, I still don't fully understand why range from A-Z wouldn't work
    – No Time
    Jan 27, 2015 at 4:53
  • 1
    @NoTime - it might, but that is a locale specific range - A-Z and a-z depend on the C locale's sort order by ASCII byte value - if you start working with locales in which that is not true, [[:lower:]] and [[:upper:]] are the robust classes - so you might as well just do it robustly from the start.
    – mikeserv
    Jan 27, 2015 at 4:55
  • I honestly started with those from the start, but was unable to get past repeating characters without having to redirect the output into a file, then append with a different regex.
    – No Time
    Jan 27, 2015 at 4:57

The solution is

grep -E '^([a-z][A-Z])+$'
  • This one did not work for me I only returned 2 items. Ill post a screen shot
    – No Time
    Jan 27, 2015 at 4:36
  • Won't match the alternating case: 'Ab'.
    – Pierre D
    Jan 27, 2015 at 6:46
  • @PierreD Correct but that's not a bug. None of the matching examples looks like that. Jan 27, 2015 at 6:59
  • 1
    @HaukeLaging Mmh. The fact that it isn't given as example doesn't make it less of an "alternating case character sequence". Examples are not the rule, they just, you know, illustrate the idea...
    – Pierre D
    Jan 27, 2015 at 7:09
  • @PierreD That is correct but very theoretical. You never know (without asking) whether the description or the examples are the better description of the intention. I have been around here for a while. I am quite familiar with that problem. Jan 27, 2015 at 7:45

You must log in to answer this question.

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