4

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.

4
  • Do you have the dc command?
    – jthill
    CommentedNov 22, 2013 at 4:07
  • @jthill, I'm afraid I don't, either :-(
    – Savir
    CommentedNov 22, 2013 at 4:10
  • Do you really have bash? Which version?
    – rici
    CommentedNov 22, 2013 at 5:13
  • 4.2.28(1)-release (mips-openwrt-linux-gnu)
    – Savir
    CommentedNov 22, 2013 at 6:28

4 Answers 4

4

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 
    1

    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 
    1
    • Clever second trick!
      – Savir
      CommentedNov 22, 2013 at 14:38
    1

    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/' 
      0

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

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

      3
      • @Stephane the quotes you added and the ones your removed are both optional, in this case. I would recommend always having them.CommentedNov 22, 2013 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).CommentedNov 22, 2013 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.CommentedNov 22, 2013 at 11:38

      You must log in to answer this question.

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.