1

I'm really struggling with writing a Bourne shell script. Basically, I have three input formats for a variable called "ref" that I'm trying to detect:

  1. ref="refs/head/.*" (i.e. begins with "refs/head/" I'm interested in the bit at the end, after the slash)
  2. ref="refs/tags/.*" (i.e. begins with "refs/tags/" I'm interested in the bit at the end, after the slash)
  3. everything else (i.e. ignore everything that doesn't begin with either "refs/head/" or "refs/tags/")

For example,

If ref="refs/head/master", set TAG="master"

If ref="refs/tags/0.2.4", set TAG="0.2.4"

For everything else, set TAG=""

Now I wrote something in bash shell, but I'm really struggling to convert it to Bourne (#!/bin/sh):

#!/bin/bash
#
#This works!
#
TAG=""
re='^refs/head/.*'   #regex: begins with refs/head/, ends with anything
re2='^refs/tags/.*'   #regex: begins with refs/tags/, ends with anything
if [[ $ref =~ $re ]]; then
        #do nothing - OK
        true   #NOP
else    
        #check if it's a tag update
        if [[ $ref =~ $re2 ]]; then
                TAG=${$ref##*/}   #looks worse that it is: http://stackoverflow.com/questions/3162385/how-to-split-a-string-in-shell-and-get-the-last-field
        fi
        exit 0
fi

echo $TAG

It took me ages to a) write this program and b) find out why my program was going nuts - turned out I need #!/bin/sh and not #!/bin/bash

How can I convert this to sh? Maybe someone else has a more elegant solution to my regex gymnastics?


Update:

Thanks for the answers sofar (especially @gboffi). I think I'm almost there.

All I need now is to know if $TAG comes from "refs/head/", "refs/tags/" or neither. I tried to modify some of the answers, but really struggling with sh. I'll need to go away and learn more about sh from first principles instead of trying to hack it.

Update 2:

So after a night's sleep, I figured it out in about 20 minutes. Here is my solution:

#!/bin/sh

ref="refs/asdf/master"

TAG=""
TAG="${ref#refs/heads/}"
if [ "$ref" != "${ref#refs/heads/}" ]; then
        echo "heads"
        echo $TAG
else
        TAG="${ref#refs/tags/}"
        if [ "$ref" != "${ref#refs/tags/}" ]; then
                echo "heads"
                echo $TAG
        else
                TAG=""
        fi
fi

echo "--->$TAG"

I'm sure there's a much more elegant solution; but I just don't have the time!

Eamorr
  • 9,872
  • 34
  • 125
  • 209
  • Out of curiosity, why do you need to convert it from bash to sh? sh doesn't have built-in regex support, so it'll basically require a rewrite. – John Kugelman Dec 10 '14 at 22:06
  • @JohnKugelman - In addition, why not allow the core Unix tools like grep, sed, and awk? Does it really need to be pure shell? – Mr. Llama Dec 10 '14 at 22:15
  • @JohnKugelman I'm afraid I have trouble using `grep`, `sed` and `awk`. Could you please suggest how I might use these standard tools in a sh script toget the last element of a split on '/'? – Eamorr Dec 10 '14 at 22:26
  • If you have a fully functional solution, please take it out of the question and make it an answer, then accept the answer as the correct one. – TTT Dec 11 '14 at 18:10

4 Answers4

2

Here it is a function, defined in dash (the linux version of sh)

% dash
$ tag() {
>  TAG=""
>  [ "$1" != "${1#refs/head/}" ] && TAG="${1#refs/head/}"
>  [ "$1" != "${1#refs/tags/}" ] && TAG="${1#refs/tags/}"
>  echo "$TAG"
>}
$ tag poldo

$ tag refs/head/poldo
poldo
$ tag refs/tags/pippo
pippo
$ tag to093u0refs/head/poldo

$exit
% 
gboffi
  • 22,939
  • 8
  • 54
  • 85
  • Hi, looks very good. Thank you. I've been trying to modify slightly - I need to know if "poldo"/"pippo" come from a /refs/head/ or a /refs/tags/... – Eamorr Dec 10 '14 at 23:03
  • 1
    I have to say that your original question didn't mention the issue of identifying the tag and the issue was not reflected to the least in the code you showed us. That said, as I think that there are a few possible, different solutions, just one of them fulfilling your needs, could you please EDIT YOUR QUESTION to introduce this new issue, explaining in detail what do you need? If you're so kind to act this way I or another person may be able to give an answer related to the top question rather than to a long thread of comments. – gboffi Dec 10 '14 at 23:18
2

The most idiomatic way to handle this is with a case statement and pattern matching:

#!/bin/sh
case $ref in
  refs/head/*) true ;;                       # original behavior: no output, success
  refs/tags/*) printf '%s\n' "${ref##*/}" ;; # original behavior: emit output
  *) false # exit with an error              # original code didn't specify behavior
esac

The reason I'm exiting with an error for the case where neither refs/head/* or refs/tags/* matches is that if you wanted to exit with success in that case, you could omit the refs/head/* test entirely.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
1

Having read your description of the desired function it seems that it boils down to this:

  1. check the input argument. If it starts with refs/head or refs/master, proceed, else stop.
  2. take the terminal piece of information and set the variable TAG to it.

thus, assuming your input is in the variable REF,

TAG=""
echo $REF | egrep -q 'refs/head|refs/master'
if [ $? -eq 0 ]
then
  TAG=`echo $REF | sed "s/.*\///"`
fi

ought to do it.

(Side note for shell heads: is basename a better solution than sed in this particular case?)

AlwaysLearning
  • 796
  • 3
  • 10
1

You can use rev-parse for this, here are some examples from one of my repositories. Notice the last one produces no output, as desired.

$ git rev-parse --abbrev-ref refs/heads/master
master

$ git rev-parse --abbrev-ref refs/tags/5
5

$ git rev-parse --abbrev-ref @^

Zombo
  • 1
  • 62
  • 391
  • 407