1

For example:

srcdir = ../libc  # Src directory
prefix = /usr/local           // Installation path 

The first line is from a makefile and second from config.make.

I need to access the variable in a shell script as $srcdir and $prefix.

One solution I found is cutting all ` = ` to `=` and the part after # and // and do eval.

Is there any other easy and reliable solution?

I am on #!/bin/sh.

  • 2
    Can you post your solution? – cuonglm Aug 2 '13 at 6:15
  • 1
    What if you have scrcir = $(TOPDIR)/src? – n.m. Aug 2 '13 at 7:05
  • @Gnouc, solution posted as answer. – limovala Aug 2 '13 at 7:30
  • @n.m.Luckly my script will not have to face such situation. – limovala Aug 2 '13 at 7:31
3

You should never try and parse foreign languages in shell script. There can always be some unexpected caveat you'll run into, and it just becomes a nightmare trying to accommodate all scenarios.
Instead, use the native interpreter to parse it and give you what you want.

getmakevar() {
    makefile="$1"
    var="$2"

    make getmakevar -f -  <<EOI
include $makefile

getmakevar:
    @echo \$($var)
EOI
}

It's POSIX compliant, and will handle any and all possible formats of the makefile (as long as it's valid gnu make)


# cat Makefile
bar=baz
myvar = /foo/$(bar)

# getmakevar Makefile myvar
/foo/baz
2

I made a pure POSIX sh version. Things I like about it:

  • Entirely shell based (no calls to external commands, aside from printf)
  • It is general (the shell functions are reusable)
  • It imports the definitions into the current environment.

The things I dislike:

  • Uses eval. I tried to avoid it, but it is necessary to make the definitions without hard-coding them. I'm validating that the name is reasonable and quoting the value. That should make it safe, but I'm not 100% sure it is.
  • POSIX sh is ugly.
  • There are no local variables to functions in POSIX sh.

Well, here it is:

#!/bin/sh

trim_left() {
    _str=$1 _chrs=$2
    while [ "$_chrs" ]; do
        _chr=${_chrs%"${_chrs#?}"}
        while [ "${_str%"${_str#?}"}" = "$_chr" ]; do
            _str=${_str#?}
        done
        _chrs=${_chrs#?}
    done
    printf %s "$_str"
}

trim_right() {
    _str=$1 _chrs=$2
    while [ "$_chrs" ]; do
        _chr=${_chrs%"${_chrs#?}"}
        while [ "${_str#"${_str%?}"}" = "$_chr" ]; do
            _str=${_str%?}
        done
        _chrs=${_chrs#?}
    done
    printf %s "$_str"
}

trim_comment() {
    _str=$1 _marker=$2
    printf %s "${_str%%"$_marker"*}"
}

quote_str() {
    _str=$1 _qstr=
    while [ "$_str" ]; do
        _chr=${_str%"${_str#?}"}
        if [ "$_chr" = "'" ]; then
            _qstr=$_qstr"'\\''"
        else
            _qstr=$_qstr$_chr
        fi
        _str=${_str#?}
    done
    printf "'%s'" "$_qstr"
}

# [a-zA-Z_][a-zA-Z_0-9]
valid_vname() {
    _vname=$1
    case $_vname in
        [[:lower:][:upper:]_]*)
        ;;
        *)
            return 1
        ;;
    esac
    _vname=${_vname#?}

    while [ "$_vname" ]; do
        case $_vname in
            [[:lower:][:upper:]_[:digit:]]*)
            ;;
            *)
                return 1
            ;;
        esac
        _vname=${_vname#?}
    done

    return 0
}

ws='    
'


input_file=$1
[ "$input_file" ] || {
    printf '%s\n' 'usage: script <file>' >&2;
    exit 1
}

while IFS== read -r _name _value; do
    name=$(trim_left "$(trim_right "$_name" "$ws")" "$ws")
    case $_value in
        *'#'*)
            value=$(trim_comment "$_value" '#')
        ;;
        *'//'*)
            value=$(trim_comment "$_value" '//')
        ;;
    esac
    value=$(trim_left "$(trim_right "$value" "$ws")" "$ws")

    if valid_vname "$name"; then
        eval "$name=$(quote_str "$value")";
    fi
done < "$input_file";

echo "$srcdir"
echo "$prefix"
1
#!/bin/sh
FILENAME="<path>"
while read LINE
do
    echo "$LINE"
    if echo "$LINE" | grep -E -q "^srcdir*"
    then
        LINE=$(echo $LINE | cut -d"#" -f1) # No Comment, strip part after #
        LINE=$(echo $LINE | awk -F" // " '{print $1;}') # No Comment, strip part after //
        LINE=$(echo $LINE | tr -d ' ')
        srcdir=$(echo $LINE | tr -d '^srcdir=')
        break
    fi
done < "$FILENAME"

echo $srcdir
  • 2
    All these successive calls to grep, cut, awk, tr are more complex than this needs to be. You're missing double quotes around many variable substitutions. – Gilles Aug 2 '13 at 23:35
  • Also, are you sure that the variable you're looking for will always be called srcdir? Because that seems too hard-coded for me. – Joseph R. Aug 3 '13 at 19:59
  • 1
    Let's try this with srcdi = me :) – a CVn Aug 3 '13 at 20:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.