Search results for: “bash snippet”

  • Bash Snippet: Luhn Algorithm

    Bash Snippet: Luhn Algorithm

    I like Bash, but it isn’t well suited for some tasks. For fun I sometimes ignore that. Occasionally people seem to find this useful.

    In that spirit, here is my implementation of the popular Luhn / mod10 algorithm used in credit card, IMEI, and other number sequences.

    download [luhn.zip]

    # Returns Luhn checksum for supplied sequence
    luhn_checksum() {
            sequence="$1"
            sequence="${sequence//[^0-9]}" # numbers only plz
            checksum=0
            table=(0 2 4 6 8 1 3 5 7 9)

            # Quicker to work with even number of digits
            # prepend a "0" to sequence if uneven
            i=${#sequence}
            if [ $(($i % 2)) -ne 0 ]; then
                    sequence="0$sequence"
                    ((++i))
            fi

            while [ $i -ne 0 ];
            do
                    # sum up the individual digits, do extra stuff w/every other digit
                    checksum="$(($checksum + ${sequence:$((i – 1)):1}))" # Last digit
                    # for every other digit, double the value before adding the digit
                     # if the doubled value is over 9, subtract 9
                    checksum="$(($checksum + ${table[${sequence:$((i – 2)):1}]}))" # Second to last digit
                    i=$((i – 2))

            done
            checksum="$(($checksum % 10))" # mod 10 the sum to get single digit checksum
            echo "$checksum"
    }

    # Returns Luhn check digit for supplied sequence
    luhn_checkdigit() {
            check_digit=$(luhn_checksum "${1}0")
            if [ $check_digit -ne 0 ]; then
                    check_digit=$((10$check_digit))
            fi
            echo "$check_digit"
    }

    # Tests if last digit is the correct Luhn check digit for the sequence
    # Returns true if valid, false if not
    luhn_test() {
            if [ "$(luhn_checksum $1)" == "0" ]; then
                    return 0
            else
                    return 1
            fi
    }

    To maximize the enjoyment I’ve optimized the script a little bit in two ways:

      1) Normally every other digit of the sequence to be computed is multiplied by 2. If that result is greater than 9, then 9 is subtracted. There are only 10 single digits in base-10, so it seemed reasonable to precompute these values. This saves not only the multiplication step, but also the conditional branch (on greater/less than 9), and the occasional subtraction operation (when values were greater than 9).
      2) I prepend a 0 to the submitted sequence if there are an uneven number of digits in the sequence. The leading 0 doesn’t affect the end result, but being assured of having pairs of digits available in the while loop of our luhn_checksum() function saves us from needing to keep track of which digits should be added directly and which should be handled as described in optimization #1 above.

    If for some reason you are actually using this script, you probably will be most interested in luhn_checkdigit() and luhn_test().

    Here is an example of how this can be used:

    #!/bin/bash
    source luhn.sh


    # Example IMEI number from Wikipedia
    sample_imei="352152097374972"
    if luhn_test "$sample_imei"; then
            echo "$sample_imei might be a valid IMEI"
    else
            echo "$sample_imei is an invalid IMEI"
    fi

    # Same number with the last two digits transposed
    sample_imei="352152097374927"
    if luhn_test "$sample_imei"; then
            echo "$sample_imei might be a valid IMEI"
    else
            echo "$sample_imei is an invalid IMEI"
    fi

    # Creating a check digit for a set of numbers
    echo "35215209737497 would be a valid looking IMEI if you added a $(luhn_checkdigit "35215209737497") to the end"

    # Many credit card types also use this checksum
    sample_mastercard="5105105105105100"
    if luhn_test "$sample_mastercard"; then
            echo "$sample_mastercard might be a valid card number"
    else
            echo "$sample_mastercard in an invalid card number"
    fi
    user@host:~$ ./demo.sh
    352152097374972 might be a valid IMEI
    352152097374927 is an invalid IMEI
    35215209737497 would be a valid looking IMEI if you added a 2 to the end
    5105105105105100 might be a valid card number
  • Bash Snippet: Decimal to Binary Conversion

    Bash Snippet: Decimal to Binary Conversion

    Convert a decimal value into the binary representation and vice versa in Bash using only built-ins. If you know a better way, please let me know. To ensure a properly formatted expression for the arithmetic expansion in bin2dec, the dec2bin function prefixes zeros as needed to pad to a character count evenly divisible by 8.

    dec2bin () {
        num="$1"
        bin=""
        padding=""
        base2=(0 1)
        while [ "$num" -gt 0 ];
        do
                bin=${base2[$(($num % 2))]}$bin
                num=$(($num / 2))
        done
        if [ $((8(${#bin} % 8))) -ne 8 ]; then
                printf -v padding ‘%*s’ $((8(${#bin} % 8)))
                padding=${padding// /0}
        fi
        echo $padding$bin
    }


    bin2dec () {
            echo $((2#$1))
    }

    Examples:

    user@host:~$ dec2bin 1
    00000001
    user@host:~$ bin2dec 00000001
    1
    user@host:~$ dec2bin 255
    11111111
    user@host:~$ bin2dec 11111111
    255
    user@host:~$ bin2dec 1010101010101010
    43690
    user@host:~$ dec2bin 43690
    1010101010101010

    Perhaps not the most efficient way, but at least for small numbers it appears to be quicker than opening a subshell.

    user@host:~$ time dec2bin 43690
    1010101010101010

    real    0m0.001s
    user    0m0.000s
    sys 0m0.000s
    user@host:~$ time echo "obase=2;43690" | bc
    1010101010101010

    real    0m0.002s
    user    0m0.000s
    sys 0m0.000s
  • Bash Snippet: Trim Function

    Bash Snippet: Trim Function

    Occasionally I find myself wanting to removing leading and/or trailing occurrences of a character or string from a larger string in bash. A couple common uses are removing quotes, or stripping an unknown number of spaces leading into the data of interest. This can be done pretty easily with other methods such as awk or sed, but with the disadvantage of requiring external commands. This a pure bash implementation using only shell built-ins. It might be better to rewrite this with $BASH_REMATCH, but this is what I currently have.

    
    
    #!/bin/bash

    trim () {
        str="$1"
        match="$2"
        # trim spaces by default
        if [ -z "$match" ]; then
            match=" "
        fi
        # trim leading
        while [ "${str:0:${#match}}" == "$match" ];
        do
            str="${str:${#match}:${#str}}"
        done
        # trim tailing
        while [ "${str:$((${#str}-${#match}))}" == "$match" ];
        do
            str="${str:0:$((${#str} - ${#match}))}"
        done
        echo "$str"
    }

    #Remove leading and trailing spaces
    example="    Hello     "
    echo "`trim  "$example" " "`"

    #The second parameter is optional when removing spaces
    example="    Hello     "
    echo "`trim  "$example"`"


    #Remove leading and trailing occurences of "word"
    example="wordHelloword"
    echo "`trim  "$example" "word"`"
  • Bash Snippet: URL Encoding

    Bash Snippet: URL Encoding

    One approach would be to encode everything, but the approach I took was to just encode things I thought might be problematic to pass over the query string.

    URL (a.k.a percent) encoding of a string in bash:

    urlencode () {
            tab="`echo -en "\x9"`"
            i="$@"
            i=${i//%/%25}  ; i=${i//’ ‘/%20} ; i=${i//$tab/%09}
            i=${i//!/%21}  ; i=${i//’"’/%22}  ; i=${i//#/%23}
            i=${i//\$/%24} ; i=${i//\&/%26}  ; i=${i//\’/%27}
            i=${i//(/%28}  ; i=${i//)/%29}   ; i=${i//\*/%2a}
            i=${i//+/%2b}  ; i=${i//,/%2c}   ; i=${i//-/%2d}
            i=${i//\./%2e} ; i=${i//\//%2f}  ; i=${i//:/%3a}
            i=${i//;/%3b}  ; i=${i//</%3c}   ; i=${i//=/%3d}
            i=${i//>/%3e}  ; i=${i//\?/%3f}  ; i=${i//@/%40}
            i=${i//\[/%5b} ; i=${i//\\/%5c}  ; i=${i//\]/%5d}
            i=${i//\^/%5e} ; i=${i//_/%5f}   ; i=${i//\`/%60}
            i=${i//\{/%7b} ; i=${i//|/%7c}   ; i=${i//\}/%7d}
            i=${i//\~/%7e}
            echo "$i"
            i=""
    }

    urlencode "The sun is hot, 42 / 0 = undefined,  & 1 + 1 = 2…"