0

I would like to do String to Integer conversion, operate that integer and back to string in shell.

I have

input_sub=000
while [ -d $input_dir ]
do
    echo $input_sub
    # HERE I would like to fist convert 000 to 0
    # then add 1 to it 0-> 1
    # then convert that 1 to 001 
done

Don't mind much about the while condition. I would like to do what is described in the comments. How can I do this?

4
  • 2
    printf "%d" "000" and back printf "%03d" "0" Commented Aug 23, 2021 at 2:52
  • 1
    Not sure if this helps your use case, but you could use brace expansions as well, like this: for dir in {000..010}; do echo "dir: $dir"; done Commented Aug 23, 2021 at 10:28
  • I would start with input_sub=0, then convert to a 0-padded string as necessary, rather than going in the other direction. Commented Aug 23, 2021 at 18:29
  • Other than things like leading zeroes, this is what bash always does with arithmetic. The shell pretty much only has strings, so variables are always stored as strings; when you do arithmetic with them, it parses them as integers, does the math, then converts the result back to a string for storage. Commented Aug 23, 2021 at 18:47

1 Answer 1

5

You can do what you need in POSIX shell, but you must protect against numbers with leading zeros being interpreted as octal numbers. To do what you want, you need a way to remove the leading zeros for your conversion to a number. While bash provides a simple built-in parameter expansion that will work, in POSIX shell, you are stuck using the old expr syntax or calling a utility like sed or grep.

To trim the leading zeros using expr, you must first know how many there are. The old POSIX shell expr provides two expressions that will work. The first called index can return the index of the first character in $input_sub that is not 0. Which gives you the index (1-based) where the first non-zero digit is found. The form you can use is:

    ## get index of first non-zero digit, POSIX compliant
    nonzero=$(expr index "$input_sub" [123456789])

With the index of the first non-zero digit in $nonzero, you can use the substr expression to obtain the number without leading zeros (you know the max number of digits is 3, so obtain the substring from the index to 3), e.g.

        num=$(expr substr "$input_sub" "$nonzero" 3)  ## remove leading 0's

You need to be able to handle 000 as $inpu_sub, so go ahead and add a if .. then ... else ... fi to handle that case, e.g.

    if [ "$nonzero" -eq 0 ]; then
        num=0
    else
        num=$(expr substr "$input_sub" "$nonzero" 3)  ## remove leading 0's
    fi

Now you can simply add 1 to get your new number:

    newnum=$((num + 1))

To convert the number back to a string of 3 characters representing the number with leading zeros replaced, just use printf with the "%03d" conversion specifier, e.g.

    # then convert that 1 to 001 
    input_sub=$(printf "%03d" "$newnum")

Putting together a short example showing the progression that takes place, I have replaced your while loop with a loop that will loop 21 times from 0 to 20 to show the operation and I have added printf statements to show the numbers and conversion back to string. You simply restore your while and remove the extra printf statements for your use:

#!/bin/sh

input_sub=000
# while [ -d $input_dir ]
while [ "$input_sub" != "020" ]    ## temporary loop 000 to 009
do
    printf "input_sub: %s  " "$input_sub"
    
    # HERE I would like to fist convert 000 to 0
    # then add 1 to it 0-> 1
    
    ## get index of first non-zero digit, POSIX compliant
    nonzero=$(expr index "$input_sub" [123456789])
    if [ "$nonzero" -eq 0 ]; then
        num=0
    else
        num=$(expr substr "$input_sub" "$nonzero" 3)  ## remove leading 0's
    fi
    newnum=$((num + 1))
    
    # then convert that 1 to 001 
    input_sub=$(printf "%03d" "$newnum")
    
    printf "%2d + 1 = %2d  =>  input_sub: %s\n" "$num" "$newnum" "$input_sub"
done

Example Use/Output

Showing the conversions with the modified while loop, you would get:

$ sh str2int2str.sh
input_sub: 000   0 + 1 =  1  =>  input_sub: 001
input_sub: 001   1 + 1 =  2  =>  input_sub: 002
input_sub: 002   2 + 1 =  3  =>  input_sub: 003
input_sub: 003   3 + 1 =  4  =>  input_sub: 004
input_sub: 004   4 + 1 =  5  =>  input_sub: 005
input_sub: 005   5 + 1 =  6  =>  input_sub: 006
input_sub: 006   6 + 1 =  7  =>  input_sub: 007
input_sub: 007   7 + 1 =  8  =>  input_sub: 008
input_sub: 008   8 + 1 =  9  =>  input_sub: 009
input_sub: 009   9 + 1 = 10  =>  input_sub: 010
input_sub: 010  10 + 1 = 11  =>  input_sub: 011
input_sub: 011  11 + 1 = 12  =>  input_sub: 012
input_sub: 012  12 + 1 = 13  =>  input_sub: 013
input_sub: 013  13 + 1 = 14  =>  input_sub: 014
input_sub: 014  14 + 1 = 15  =>  input_sub: 015
input_sub: 015  15 + 1 = 16  =>  input_sub: 016
input_sub: 016  16 + 1 = 17  =>  input_sub: 017
input_sub: 017  17 + 1 = 18  =>  input_sub: 018
input_sub: 018  18 + 1 = 19  =>  input_sub: 019
input_sub: 019  19 + 1 = 20  =>  input_sub: 020

This has been done in POSIX shell given your tag [shell]. If you have bash available, you can shorten and make the script a bit more efficient by using bash built-ins instead of expr. That said, for 1000 directories max -- you won't notice much difference. Let me know if you have further questions.

Bash Solution Per-Request in Comment

If you do have bash available, then the [[ ... ]] expression provides the =~ operator which allows an extended REGEX match on the right hand side (e.g. [[ $var =~ REGEX ]]) The REGEX can contain capture groups (parts of the REGEX enclosed by (..)), that are used to fill the BASH_REMATCH array where ${BASH_REMATCH[0]} contains the total expression matched and ${BASH_REMATCH[1]} ... contain each captured part of the regex.

So using [[ ... =~ ... ]] with a capture on the number beginning with [123456789] will leave the wanted number in ${BASH_REMATCH[1]} allowing you to compute the new number using the builtin, e.g.

#!/bin/bash

input_sub=000
# while [ -d $input_dir ]
while [ "$input_sub" != "020" ]     ## temporary loop 000 to 020
do
    printf "input_sub: %s  " "$input_sub"
    
    # HERE I would like to fist convert 000 to 0
    # then add 1 to it 0-> 1
    
    ## [[ .. =~ REGEX ]], captures between (...) in array BASH_REMATCH
    if [[ $input_sub =~ ^0*([123456789]+[0123456789]*)$ ]]
    then
        num=${BASH_REMATCH[1]}      ## use number if not all zeros
    else
        num=0                       ## handle 000 case
    fi
    newnum=$((num + 1))
    
    # then convert that 1 to 001 
    input_sub=$(printf "%03d" "$newnum")
    
    printf "%2d + 1 = %2d  =>  input_sub: %s\n" "$num" "$newnum" "$input_sub"
    
done

(same output)

Let me know if you have further questions.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much for your answer. I will add one tag bash. Could you comment a bit more about the last part (bash part)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.