Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Let's say I have two bash variables that contain binary values:

a=0011 # decimal 3
b=1000 # decimal 8

Is there a way I can loop through all the possible values between $a and $b keeping it binary? Something like:

for blah in $(seq $a $b) ; do
    print "Blah is: $blah"
done

So it will output:

Blah is: 0011
Blah is: 0100
Blah is: 0101
Blah is: 0110
Blah is: 0111
Blah is: 1000

I have tried:

for blah in $(seq "$((2#$a))" "$((2#$b))") ; do

But then $blah becomes decimal, and I'd like to keep it a binary (I can always transform the decimal back to binary, but that seems a waste, since I alredy have the extremes in binary)

This code must run in a limited linux (OpenWRT) that doesn't have obase available. If the answer is that it's not possible to keep the binary value, that's a useful answer as well (I can create a function that converts decimal to binary without using obase) Besides, it can be a useful answer to people using regular bash.

share|improve this question
    
Do you have the dc command? –  jthill Nov 22 '13 at 4:07
    
@jthill, I'm afraid I don't, either :-( –  BorrajaX Nov 22 '13 at 4:10
    
Do you really have bash? Which version? –  rici Nov 22 '13 at 5:13
    
4.2.28(1)-release (mips-openwrt-linux-gnu) –  BorrajaX Nov 22 '13 at 6:28

4 Answers 4

up vote 0 down vote accepted

seq is not a built-in. It's also not part of the Posix standard. But the usual implementations of seq don't have any ability to sequence in bases other than 10.

In bash, you can specify a range as {start..finish}. However, that also doesn't work in bases other than 10 (although it does work with letters: {a..f} expands to a b c d e f.

And as far as I know, that's it for simple sequence generators, which leaves you with a couple of possibilities.

The silly way to do it is to filter out the non-binary values. That's simple but hugely inefficient if a and b aren't tiny:

for x in $(seq -w $a $b); do
  if [[ ! ($x =~ [2-9]) ]]; then
    echo $x
  fi
done

Here's a better solution. Assuming a and b are the same length (if not, you can use printf to fix that), the following will loop through all binary numbers from a to b, inclusive:

# We need a string of 0s at least as long as a:
z=${a//1/0}
while [[ ! ($a > $b) ]]; do
  # do something with $a
  # The following "increments" a by removing the last 0 (and trailing 1s)
  # and replacing that with a 1 and the same number of 0s.
  a=$(printf "%.*s" ${#a} ${a%0*}1$z)
done
share|improve this answer
    
Clever second trick! –  BorrajaX Nov 22 '13 at 14:38

You're close with this:

for blah in $(seq "$((2#$a))" "$((2#$b))") ; do

You just need to convert the decimal values from the for loop back into binary with something like dc.

Example

$ for blah in $(seq "$((2#0011))" "$((2#1000))"); do \
     printf "%04d\n" $(echo "obase=2;$blah" | bc);done
0011
0100
0101
0110
0111
1000

The printf is being used to control the output so that it's padded with leading zeros, and formatted to a certain width. The arguments, %04d is what specifies the output.

The other key pieced to this command is the use of bc - a command line calculator. This command for example:

echo "obase=2;$blah" | bc

Is taking the value, $blah and converting it to base 2 (aka. Binary) through the use of bc.

Without bc or dc

If you're on a system that's limited such that neither of these tools are present you can do the conversion directly using just awk and this function from awk's manual.

Example

Create a file with the following content, call it dec2bin.awk.

# bits2str --- turn a byte into readable 1's and 0's

function bits2str(bits, data, mask)
{
    if (bits == 0)
        return "0"

    mask = 1
    for (; bits != 0; bits = rshift(bits, 1))
        data = (and(bits, mask) ? "1" : "0") data

    while ((length(data) % 8) != 0)
        data = "0" data

    return data
}

{
  printf("%s\n", bits2str($1))
}

Now to use the above function:

$ for blah in $(seq "$((2#0011))" "$((2#1000))"); do echo $blah \
     | awk -f dec2bin.awk; done
00000011
00000100
00000101
00000110
00000111
00001000
share|improve this answer

It's easier with zsh:

for ((i=2#$a; i<=2#$b; i++)) echo $(([##2]i))

Or with 0-padding:

for ((i=2#$a; i<=2#$b; i++)) printf '%04d\n' $(([##2]i))

Otherwise you can use bc:

echo "ibase=obase=2; for (i=$a; i<=$b; i++) i" | bc

Or dc:

echo "2doi $a [p1+d$b!<a]dsax" | dc

To 0-pad, you can always pipe the output to:

sed 's/^/000/;s/^0*\(.\{4\}\)/\1/'
share|improve this answer

This method will do a lot of processing, so not good for large ranges.

seq -f '%04g' 0011 1000 | grep -v '[2-9]'

share|improve this answer
    
@Stephane the quotes you added and the ones your removed are both optional, in this case. I would recommend always having them. –  richard Nov 22 '13 at 10:42
1  
No, the quotes around [2-9] are not optional. It will cause it to fail with csh, tcsh or zsh if there's no file matching that pattern in the current directory, or worse with other shells (not fish), it will fail badly and more obscurely if there are files matching that pattern in the current directory. You're right that quotes around % are necessary for the fish shell. single quotes are better than double quotes as in your earlier version since that means it also works with rc and es. As it is written now, it would work with any shell (on systems where GNU seq is installed). –  Stéphane Chazelas Nov 22 '13 at 11:01
    
@stephane Sorry I missed those cases where it did not work, this is why I recommend using quotes where ever they may be needed. –  richard Nov 22 '13 at 11:38

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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