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="${sequence//[^0-9]}" # numbers only plz
        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
        if [ $(($i % 2)) -ne 0 ]; then

        while [ $i -ne 0 ];
                # 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))

        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))
        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
                return 1

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:

source luhn.sh

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

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

# 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
if luhn_test "$sample_mastercard"; then
        echo "$sample_mastercard might be a valid card number"
        echo "$sample_mastercard in an invalid card number"
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
Published on :Posted on

3 thoughts on “Bash Snippet: Luhn Algorithm”


Very nice! I’ve wrapped this in a script on my github, hope that is OK:


Could you please add license and copyright in the code to simplify reusing it?

For example this is what I put in my code (REUSE-compliant):
# SPDX-FileCopyrightText: (c) 2024 ale5000
# SPDX-License-Identifier: GPL-3.0-or-later

Post your comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.