15

I want to write a bash script to print all possible lower and upper case permutations of a certain word, e.g. harley:

harley
harleY
harlEy
harLey
...
HARLey
HARLEy
HARLEY

My naive solution is to write a n-th (n is len(word)) nested for-loop for this specific word:

#!/bin/bash
for a in {h,H}; do
    for b in {a,A}; do
    ...
    done
done

However, I would have to code the script for a different word again.

Is there a better way to accomplish this?

6 Answers 6

22

A slightly better solution:

echo {h,H}{a,A}{r,R}{l,L}{e,E}{y,Y}

For full scalability:

echo harley \
| perl -nle 'print "echo ",
                    join "",map { "{" . lc . "," .uc ."}" } split //' \
| xargs -I {} bash -c "{}"

If you absolutely must have one word per line, go with

for w in {h,H}{a,A}{r,R}{l,L}{e,E}{y,Y};do echo $w;done

thanks to mattdm's comment

The corresponding scalable version would be:

echo harley \
| perl -nle 'print join "",map { "{" . lc . "," .uc ."}" } split //' \
| xargs -I {} bash -c 'for w in {};do echo $w;done'

For fun, try replacing "harley" with "supercalifragilisticexpialidocious" It's been 5 minutes and my computer is still crunching on this one and will probably never finish :)

3
  • 1
    for w in {h,H}{a,A}{r,R}{l,L}{e,E}{y,Y}; do echo $w;done
    – mattdm
    Commented Nov 16, 2014 at 4:58
  • 4
    A still simpler one-per-line solution: printf '%s\n' {h,H}{a,A}{r,R}{l,L}{e,E}{y,Y}
    – John1024
    Commented Nov 16, 2014 at 7:42
  • 2
    @John1024 I encourage you to post that as an answer, it is an under-appreciated feature of bash's printf Commented Nov 16, 2014 at 13:12
12
eval echo $(echo "word" | sed 's/./{\U&,\L&}/g')
  • sed 's/./{&,&}/g' would turn Foo into {F,F}{o,o}{o,o}, which would be pretty useless.  But add \U and \L and you get the upper and lower case of each letter; i.e., {F,f}{O,o}{O,o}.
  • Then it’s a simple matter of using eval to tell the shell to expand the {X,x} brace sequences.
1
  • 1
    Nice trick :). If I could accept two answers, yours would be accepted, too! Upvote anyway
    – polym
    Commented Nov 19, 2014 at 10:25
4

EDIT 2: This answer is wrong. It doesn't produce 2^n combinations as it should.

EDIT: I don't know why, but this solution is realy fast compared to the perl solution by @Joeseph R. It runs "Supercalifragilisticexpialidocious" in less than 0.3 seconds!

Here's my crack at it:

#!/bin/bash

str=${1^^}  # convert to uppercase
len=${#str} # get length of string

for ((perm=0; perm <= len; perm++)); do
    for ((i=0; i <= len; i++)); do
        lower=${str,,}   # convert to lowercase

        # Uppercase n-th letter for permutation
        if [ $perm -gt 0 ]; then
            nth=${lower:perm-1}
            lower=$(echo ${lower:0:perm-1}${nth^})
        fi

        echo -n ${str:0:i} # print orig string from 0 to $i
        echo ${lower:i}    # print new string from $i to end
    done
done | sort -u

Running it:

$ ./permutations.sh hi
hi
hI
Hi
HI

$ ./permutations.sh harley
harley
harleY
harlEy
harLey
haRley
hArley
Harley
HarleY
HarlEy
HarLey
HaRley
HArley
HArleY
HArlEy
HArLey
HARley
HARleY
HARlEy
HARLey
HARLeY
HARLEy
HARLEY

Feel free to fork and modify it, I'm sure it can be optimized. https://gist.github.com/ryanmjacobs/4c02ad80f833dee0c307

2
  • 1
    The code clearly does not print all results. With harley you should have 64 results, where's harLEY, for example?
    – Denis
    Commented Nov 17, 2014 at 7:25
  • 1
    @Denis Yup you're right. Every time there should be 2^n results, where n is the number of characters of the original string. This answer is wrong. Commented Nov 18, 2014 at 0:50
2

With zsh:

set -o extendedglob # usually in ~/.zshrc

word=Harley
eval print -rl ${word//(#m)?/{$MATCH:l,$MATCH:u}}
1

I wrote a few simple functions based on the best answer that will do output in the desired format.

to_lower () 
{ 
    tr '[[:upper:]]' '[[:lower:]]' <<< $@
}
to_upper () 
{ 
    tr '[[:lower:]]' '[[:upper:]]' <<< $@
}
generate_permutations () 
{ 
    local perm="$@";
    perl -e '@foo = split(//, $ARGV[0]);foreach my $c (@foo){print "$c $c\n";}' ${perm} | while read l u; do
        echo "{$(to_lower ${l}),$(to_upper $u)}";
    done | tr -d '\n';
    echo
}

Usage/example:

$ generate_permutations foobar
{f,F}{o,O}{o,O}{b,B}{a,A}{r,R}
3
  • 1
    (1) Please don’t say <<< $@.  (2) Please don’t say var="$@".  (3) Please don’t say ${var} (or $var).  Say "$var", or "${var}" if it’s really appropriate.  (4) Please don’t post answers that are wider than the display.  (5) You generally don’t need to end shell commands with ;.  (6) It looks like you have 93% of an answer.  The question asks for foobar, foobaR, foobAr, ….  Your answer doesn’t actually generate that. Commented May 2, 2022 at 17:02
  • 1
    (Cont’d) …  (7) Which answer do you claim that yours is “based on”? Joseph R.’s? (8) Your answer invokes tr 2 × n times. Do you believe that that is better than doing all the case manipulation in Perl? (9) You claim that your answer is “simple”. How do you justify that claim, given that yours is one of the longest answers? Commented May 2, 2022 at 18:26
  • @G-ManSays'ReinstateMonica' That was just an example that I gave, it supports any argument, I didn't say it was better than his just tuned to generate for any word that is given. No I don't believe it is better than doing it in perl, and I suppose I could have used tr within perl. I use declare -pf after creating functions for appropriate white spacing and it adds that otherwise they just become oneliners.
    – linuxgeek
    Commented May 3, 2022 at 16:29
0

Improving on Joseph R's answer, this one shouldn't cause duplicates for characters that aren't a-z (improving processing time):

echo s.t.r | perl -nle 'print join "", map { /[a-zA-Z]/ ? "{" . lc . "," .uc ."}"  : $_ } split //' | xargs -I {} bash -c 'for w in {};do echo $w;done'
s.t.r
s.t.R
s.T.r
s.T.R
S.t.r
S.t.R
S.T.r
S.T.R

You must log in to answer this question.

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