Evan Langlois
Evan Langlois

Reputation: 4353

printf issues in bash

OK, here's the code

printf "%0.s1" $(seq $1)

Simple enough, it sits in a function and you pass in a length and it gives you a bunch of 1s. This function then pads 0s to create a string of 32 bits which another function changes to an integer or IP. For example:

cdr2mask() {
    local BIMASK=$(printf "%-32s" $(printf "%0.s1" $(seq $1)) | tr ' ' '0')
    integerToIP $(convertbaseNumFromTo $BIMASK 2 10)
}

This function works fine when called directly (argument is the number of bits in your netmask, ex: 24). But, when called from the following, it fails. It gives a 1 followed by 31 0s instead of the correct mask. It gets $1 correctly. I've narrowed it down to the printf statement (the one at the top) only returning a single 1 when called this way. The function calling cdr2mask is:

ipInNetwork() {
    local IFS HOST NETWORK NETMASK CIDR
    read HOST NETWORK NETMASK <<<$*
    if [ -z "$NETMASK" ]; then
        IFS='/'
        read NETWORK CIDR <<<"$NETWORK"
        if [ -z "$CIDR" ]; then
            NETMASK="255.255.255.255"
        else
            NETMASK=$( cdr2mask "$CIDR" )
        fi
    fi
    if [ $(( $( ipToInteger $HOST ) & $( ipToInteger $NETMASK ) )) = \
    $(( $( ipToInteger $NETWORK ) & $( ipToInteger $NETMASK ) )) ]; then
        return 0;
    else
        return 1;
    fi
}

The following versions of cdr2mask work fine, so its not the caller:

newcdr2mask() {
    local ONES="11111111111111111111111111111111"
    local BINUM=$(printf "%-32s" ${ONES:0:$1} | tr ' ' 0)
    integerToIP $( convertbaseNumFromTo $BINUM 2 10 )
}

oldcdr2mask () 
{
   # Number of args to shift, 255..255, first non-255 byte, zeroes
   set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0
   [ $1 -gt 1 ] && shift $1 || shift
   echo ${1-0}.${2-0}.${3-0}.${4-0}
}

The last one I didn't write. Of course, suggestions on speed improvements and other suggestions are welcome. I'm not getting the speed I really want here, but I'm only just starting to optimize. The "old" method may still be the fastest, but I want to get the other version working so I can test it. Could this be a bash bug?

Upvotes: 1

Views: 214

Answers (1)

Mat
Mat

Reputation: 206689

The culprit:

IFS='/'

This breaks the seq expansion - seq outputs newlines as separators (by default), and the shell doesn't do word splitting on those when IFS is /. (local IFS doesn't help here, local variables are visible in the called functions.)

I'd avoid setting IFS entirely, by doing something like this instead of the read:

CIDR=${NETWORK##*/}
NETWORK=${NETWORK%/*}

(With a case statement to deal with the cases for NETWORK containing a / or not.)

Upvotes: 1

Related Questions