views:

182

answers:

3

I have this function, works fine, but I would like to rewrite it in bash. the problem is, I have too little knowledge of what's available in bash.

#!/usr/bin/python

def parse_svnversion(value):
    """split the output of svnversion into its three components

    given a string that looks like the output of the command
    svnversion, returns the 3-tuple (low, high, flags)

    >>> parse_svnversion('1024')
    (1024, 1024, '')
    >>> parse_svnversion('1024:2000')
    (1024, 2000, '')
    >>> parse_svnversion('1024M')
    (1024, 1024, 'M')
    >>> parse_svnversion('1024:2000MP')
    (1024, 2000, 'MP')
    """

    values = filter(lambda x: x.isdigit() or x==':', value).split(':')
    return int(values[0]), int(values[-1]), filter(str.isalpha, value)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

what I would like is a similarly small bash function that I can invoke and that will set something (three variables? an array?) that I can use. if it's an array, I would really like it to be of fixed size (3).

A: 

you can use this subroutine

parsesvn(){
 toparse="$1"
 num=${toparse%%[A-Z]*}
 alpha=${toparse##*[0-9]}
 IFS=":"
 set -- $num
 for i in $@
 do
    printf "%s " $i
 done
 if [ ! -z "$alpha" ];then
    printf "%s" "$alpha"
 fi
}

# main #
var=$(parsesvn "1024:2000")
set -- $var
if [ "$1" -lt "$2" ];then
    echo "ok"
    greater=$2
else
    echo "LHS: $1 greater than RHS: $2"
fi
echo "greater is $greater"
the `alpha=${toparse##*[0-9]}` part is useful, thanks, but splitting the `toparse` into an array... I'd like guaranteed two elements there, low and high, even if they are equal.
mariotomo
what do you mean low and high? do you mean to check if the LHS number is smaller than RHS number?
yes, I want to give a warning to the user: "you're working with mixed revisions". and further I need to work with the highest revision number. if there are flags, I also want to warn the user about them.
mariotomo
i am only following closely, if not 100% what your Python code is doing. The best i can do, since i am not really familiar with svn, is add code to check for greater or lesser part, and get the greater number, and you can proceed from there. In the end, you still have to get familiar with a bit of shell programming if you are going to convert that Python code to shell.
A: 

The following solution stores the values into the array arr[ ] to match your original tuple as closely as possible. After the if-else-fi block you can do whatever you want with arr[0],arr[1], and arr[2]. I tried to match your post (and comment) as closely as possible. Also, I took the liberty to send the warning and notice messages to STDERR rather than STDOUT thinking you probably want to separate those.

#!/bin/bash

parse_svnversion()
{
    if [[ "$1" = *:* ]]; then
        arr[0]=${1%:*}
        arr[2]=${1//[0-9:]/}
        tmp_arr[1]=${1#*:}
        arr[1]=${tmp_arr[1]//${arr[2]}/}
    else
        arr[2]=${1//[0-9:]/}
        arr[0]=${1//${arr[2]}/}
        arr[1]=${arr[0]}
    fi

    echo ${arr[@]} 

    head_rev=$( (( ${arr[0]} > ${arr[1]} )) && echo ${arr[0]} || echo ${arr[1]} )
    echo "Notice: head revision is $head_rev" >&2

    if (( ${arr[1]} < ${arr[0]} )); then
        echo "Warning: you're working with mixed revisions" >&2
    fi
    if [[ -n ${arr[2]} ]]; then
        echo "Warning: there are flags" >&2
    fi
}

parse_svnversion "1024"
parse_svnversion "1024:2000"
parse_svnversion "1024M"
parse_svnversion "1024:2000MP"
parse_svnversion "2000:1024M"

Result without STDERR (sent to /dev/null)

$ ./svn_split.sh 2> /dev/null
1024 1024
1024 2000
1024 1024 M
1024 2000 MP
2000 1024 M

Result with STDERR

$ ./svn_split.sh
1024 1024
Notice: head revision is 1024
1024 2000
Notice: head revision is 2000
1024 1024 M
Notice: head revision is 1024
Warning: there are flags
1024 2000 MP
Notice: head revision is 2000
Warning: there are flags
2000 1024 M
Notice: head revision is 2000
Warning: you're working with mixed revisions
Warning: there are flags
SiegeX
+3  A: 

This creates an array called "tuple" with three elements:

[[ $(svnversion .) =~ ([0-9]+):*([0-9]*)([A-Z]*) ]]
tuple[0]=${BASH_REMATCH[1]}
tuple[1]=${BASH_REMATCH[2]:-${tuple[0]}}
tuple[2]=${BASH_REMATCH[3]:-''}

Requires Bash 3.2 or greater. It may work in Bash >= 3 and < 3.2. Not portable to the Bourne shell, although it can be adapted for the Korn shell or the Z shell.

ksh uses the .sh.match array variable, for example: ${.sh.match[1]}

zsh uses the match array variable, for example: ${match[1]} or you can do

setopt bashrematch ksharrays

to have it work with the Bash version exactly as above.

The brace substitutions should be the same for all three.

Dennis Williamson
+1. I knew about the =~ operator but never knew that bash could do essentially back references with $BASH_REMATCH[@]. Might be worth mentioning that this script is non-portable and requires Bash 3 or greater.
SiegeX
I _knew_ it was possible to do it in a _compact_ way!thanks.
mariotomo
According to the Bash Changelog http://cnswww.cns.cwru.edu/~chet/bash/CHANGES , Chet added the =~ operator along with BASH_REMATCH[@] in 3.0-alpha
SiegeX
That's true, but 3.2 had this change: "Quoting the string argument to the [[ command's =~ operator now forces string matching, as with the other pattern-matching operators." which is what I was referring to, but doesn't apply here. Bash 4 introduces `shopt -s compat31` to affect this behavior.
Dennis Williamson