Korn expr substr fails for non-numeric value

I am running AIX 5.3 using the Korn Shell. I am reading file names from a file, as an example:

E0801260
E0824349
E0925345
EMPMSTR

statement "num=$(expr substr "$DDNAME" 4 2)

extracts the numeric values fine. But when I het the last entry, it returns num=MS, but I get an error messages "The specific number is not valid for this command".

My goal is to read thru this file and skip items like the last one that is NOT numeric. Am I going down the wrong path? Is there a better way of handlling this task?

Thanks!

Just add a simple judgement to see if the sub matches \[0-9][0-9]\ or not.

        sub=$(expr substr "$line" 4 2)
        val=$(expr "$sub" : '\([0-9][0-9]\)')
        if [[ -n "$val" ]]; then
                echo " is a number"
        else
                echo " not a number"
        fi

There is no need to use expr.

#!/bin/ksh93

while read LINE
do
   sub=${LINE:4:2}
   case $sub in
      ( +([0-9]) )  echo "$sub is a number" ;;
      *)            echo "$sub is not a number" ;;
   esac
done < file

gives

12 is a number
43 is a number
53 is a number
ST is not a number

How about:

num=${DDNAME:3:2}
if [[ $num =~ [0-9]+ ]]; then
  command $num
fi

my ksh does not support sub=$(line:4:2);
I have to use sub=$(expr substr $line 4 2);

You have to use:

sub=${line:3:2}

(curly brackets)

Why I still got the error:

#!/bin/ksh
cat "num_str.txt" | while read line
do
        echo "line:$line"
        sub=${line:3:2}
        echo "sub:$sub"
        #sub=$(expr substr "$line" 4 2)
        #val=$(expr "$sub" : '\([0-9][0-9]\)')
        #if [[ -n "$val" ]]; then
        #       echo " is a number"
        #else
        #       echo " not a number"
        #fi
done

Gave me the error:

$ ksh num_str.ksh
line:E0801260
num_str.ksh[14]: : bad substitution

Ow that is right you must have ksh88. Then it will not work. You do not happen to have /usr/dt/bin/dtksh on your system or /bin/ksh93 ?

Thanks everyone for the responses. I have tried freelong's solution and that worked for me. I will also try some of the others to see if they will fit into my design.

I am starting to see entries about ksh93(?) and I will see if I have the lastest Korn release.

Again, thanks all !

---------- Post updated at 08:20 AM ---------- Previous update was at 07:56 AM ----------

I am trying now Scrutinizer's solution and getting an error. I am running ksh93.

num=${DDNAME:3:2}
if [[ $num =~ [0-9]+ ]]
then
echo "$num is numeric"
else
echo "$num is NOT numeric"
fi

Error: ./kwb006[29]: 0403-057 Syntax error at line 32 : `=~' is not expected.

It should work. What version of ksh93 are you using?

echo ${.sh.version}

It did not work. But, on first line I use /bin/ksh. So, I added /bin/ksh93:

VersionM-12/28/93e

Should I start my Korn shell scripts with ksh93?

If you know you can run it on all the machines that the scripts need to run on, I most certainly would. ksh88 is so eh... 1988 :slight_smile:

And ksh93 syntax is less portable.

This function tests whether its first argument is a positive integer in any Bourne-type shell:

is_int()
{
  case $1 in
     *[!0-9]*|"") return 1 ;;
  esac
}

Humm, whether your shell scripts are portable or not mostly depends on how you write your shell scripts and not on the particular shell you choose to use.

Every modern shell contains a core set of functionality which is portable across most platforms and architectures together with extended functionality which certain groups of users find useful.

Provided a shell script is written to only use the core functionality, it will generally be portable.

In AIX (including 5.3) /bin/ksh is a ksh88, whereas /bin/ksh93 is a ksh93. The substr()-function in the form "${var:start:length}" is only implemented in ksh93.

cfajohnsons observation is correct in a very general way: using ksh93-functions always runs the risk of getting less portable. On the other hand: using an external tool (expr, sed, awk, whatever) to trim the variables content is expensive in terms of system calls. I would like to offer the following solution to this problem, which which work in every ksh.version:

sub1=${DDNAME%${DDNAME#?????}}
sub=${sub1#???}

The first line extracts the first 5 characters from the string $DDNAME, the second statement extracts the last 2 from these 5 characters, effectively giving the substring starting at pos 4, length 2. Because this is done completely in the shell and without calling an external program it shoulld be by far faster than any solution based on such a program.

cfajohnson already explained how this can be tested for being an integer or not.

I hope this helps.

bakunin

And in bash since version 2 (and probably zsh).

That will work in any POSIX shell.

The following substr functions will work in any POSIX shell and don't use any external commands.

_substr()
{
  _SUBSTR=

  ## store the parameters
  ss_str=$1
  ss_first=$2
  ss_length=${3:-${#ss_str}}

  ## return an error if the first character wanted is beyond end of string
  if [ $ss_first -gt ${#ss_str} ]
  then
    return 1
  fi

  if [ $ss_first -gt 1 ]
  then

    ## build a string of question marks to use as a wildcard pattern
    _repeat "?" $(( $ss_first - 1 ))

    ## remove the beginning of string
    ss_str=${ss_str#$_REPEAT}
  elif [ ${ss_first} -lt 0 ] ## ${#ss_str} ]
  then
    ## count from end of string
    _repeat "?" ${ss_first#-}

    ## remove the beginning
    ss_str=${ss_str#${ss_str%$_REPEAT}}
  fi

  ## ss_str now begins at the point we want to start extracting
  ## print the desired number of characters
  if [ ${#ss_str} -gt $ss_length ]
  then
    _repeat "${ss_wild:-??}" $ss_length
    ss_wild=$_REPEAT
    _SUBSTR=${ss_str%${ss_str#$ss_wild}}
  else
    _SUBSTR=${ss_str}
  fi
}

substr()
{
  _substr "$@" && printf "%s\n" "$_SUBSTR"
}
_repeat: command not found
_repeat()
{
  ## If the first argument is -n, repeat the string N times
  ## otherwise repeat it to a length of N characters
  case $1 in
    -n) shift
    r_num=$(( ${#1} * $2 ))
    ;;
    *) r_num=$2
    ;;
  esac
  r_str=$1
  _REPEAT=$1
  while [ ${#_REPEAT} -lt ${r_num} ]
  do
    if [ $(( ${#_REPEAT} * 2 )) -gt $r_num ]
    then
      while [ ${#_REPEAT} -lt $r_num ]
      do
        _REPEAT=$_REPEAT$r_str
      done
    elif [ $(( ${#_REPEAT} * 2 )) -eq $r_num ]
    then
      _REPEAT=$_REPEAT$_REPEAT
    else
      ## The length builds rapidly by concatenating 3 copies in each loop
      ## Your results may be different, but I have found that three is
      ## the optimum number; try it with more, if you like
      _REPEAT=$_REPEAT$_REPEAT$_REPEAT
    fi
  done
  while [ ${#_REPEAT} -gt $r_num ]
  do
    _REPEAT=${_REPEAT%?}
  done
}

We could also make use of the printf statement:

substr()
{
  string=$1
  pos=$2
  len=$3
  while [ $pos -gt 0 ]            #- Remove characters before pos
  do
    string=${string#?}
    pos=$(( pos-1 ))
  done
  printf "%.${len}s\n" "$string"  #- print len characters
}

I have not tested this, but if your input data only contains letters and numbers then maybe something like this would work.

extractNumbers()
{
oldIFS="$IFS"
IFS="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
set -- $1
IFS=${oldIFS}
unset oldIFS # Old Bourne Shells do not support typeset on variable definiions
# remove empty arguments
for a in $*
do
# or add logic to use arithmetic operators to extract your number portion or whatever else you want before
# echoing the result back
echo $1
break
done
}