2

I'm trying to figure out a way to read a C header file that contains some #define's and convert those macros to bash variables that can be accessed by a shell script.

What I tried first was this:

gcc -E -dM header_file.h|grep '^#define'|awk '{var=$2;$1=$2="";sub(/^[ \t]+/, "");print "export " var "=\"" $0 "\""}' > /tmp/tmp.sh
source /tmp/tmp.sh

Which worked, but I really didn't like that I was generating this temporary file

What I tried next was similar, in hopes of getting rid of the temporary file:

gcc -E -dM  header_file.h|grep '^#define'|awk '{var=$2;$1=$2="";sub(/^[ \t]+/, "");print "export " var "=\"" $0 "\""}' |while read i
do
$i
done

This did not work, however... It seems like there should be another way to do this, but I can't think of it, and I'm pretty sure once somebody posts how I'm going to feel really stupid, but I'd still appreciate the kickstart that anyone can offer to point me in the right direction.

4
  • 1
    Why not (while read -r def var val; do printf "export %s=\"%s\"\n" "$var" "$val"; done < <(gcc -E -dM header_file.h|grep '^#define')) >tmp/tmp.sh && source /tmp/tmp.sh? You can probably drop the tmp file altogether and just add source < before the rest of the subshell expression (...) Commented Nov 25, 2017 at 7:54
  • Nice... I like the idea of using read and printf instead of awk. I'm not able to figure out what you mean by how to drop the tmp file though... I tried putting source < in front of the entire expression and it complained bitterly about a syntax error near the open parenthesis. Commented Nov 25, 2017 at 8:28
  • Well I was simply thinking about another redirection from the entire process (...) to source as a file. Meaning redirect the entire command source < (while read -r ...^#define')) Commented Nov 25, 2017 at 9:04
  • If you edit your question to include concise, testable sample input (i.e. the output of your gcc command) and expected output then I'm sure someone will be able to help you do whatever you're trying to do concisely and robustly. Commented Nov 26, 2017 at 3:34

3 Answers 3

3

A sophisticated solution:

#!/bin/bash

source <(\ 
    sed -r '{
        /^#define/!d
        /#define[ \t]*[^ \t]*$/d
        s/[^ \t]*[ \t]*([^ \t]*)[ \t]*(.*)/\1="\2"/
        /(""|\([^\)]*\))/d
    }' <(gcc -E -dM HEADERFILE)
)

where

/^#define/!d keeps lines starting with #define

/#define[ \t]*[^ \t]*$/d deletes lines with empty macro definitions

s/[^ \t]*[ \t]*([^ \t]*)[ \t]*(.*)/\1=\2/ creates equalities

, the process is substituted and the descriptor can be sourced.

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

8 Comments

At first I thought this wasn't working, because I was manually invoking my script via sh, and it complained about a syntax error with the < character. But explicitly running the script via bash works, or changing it to executable and putting #!/bin/bash at the top makes it work. This is peculiar, however, because running sh --version, I can see that it is the exact same version of bash that I ordinarily use for a shell. Any ideas why?
Oh, and also the \1=\2 also needed to be changed to \1=\"\2\" for my purposes, to support definitions that had any whitespace in them
Yea, this is made to be run as a script. Sorry for not telling You. There are no line separators at the ends of the lines, so if you just paste this code to the terminal, it will run badly. My bash and sh versions are the same btw, both are 4.4.12 GNU.
I didn't even know its valid to define constant including whitespaces without quotes.
I think I was confused as well, cos I was just using sed on the header file, not on the gcc output, but gcc doesn't seem to use quotes. Now added quote to the code. I don't really know how you need to use this variables, but you can play with the regexps to create any other equalities.
|
1
eval $(sed -n 's/^#define  *\([^ ]*\)  *\(.*\) *$/export \1=\2/p' file.c)

Comments

1

The source <(...) syntax requires Bash 4, doesn't work in Bash 3 or lower.

Using eval is evil. And actually source-ing is not better anyway.

This should be reasonably safe, and portable:

while read -r def var val; do
    printf -v $var "$val"
done < <(gcc -E -dM header_file.h | grep -E '^#define[ \t]+[a-zA-Z_][0-9a-zA-Z_]+')

3 Comments

As a point of interest, source <(...) also doesn't seem to work if you invoke the script using sh, instead of bash explicitly, even if sh is just a symbolic link to bash.
@markt1964 An sh that is a symbolic link to Bash, will behave exactly as the linked Bash. If that bash is version 4 or higher, then source <(...) will work, otherwise not. Using Bash directly or a sh that is a symlink to Bash is not relevant.
I have GNU Bash 4.4.12(1) on my system and /bin/sh is just a symlink to /bin/bash, but it does not work for me. Invoking the script through /bin/sh script.sh gives a "syntax error near unexpected token '('", while invoking it through /bin/bash does not, and behaves as expected. It's not impossible for programs to have different behavior programmed into them depending on the name they are invoked as, but this particular case is peculiar, as I've found that variables like $BASH_VERSION and some other bash-only characteristics work fine.

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.