Adarsh
Adarsh

Reputation: 893

Shell Script with C Preprocessor directive

I want to use C preprocessor directive with shell script. Since C preprocessing stage does not compile the entire script. Thus it should be possible to use C preprocessor directives as it would be considered as comment for the script. It would be helpful as well in a project to maintain a single copy with multiple changes incorporated under the macro.

Here is sample code I wrote for illustration:

#ifdef HELLO
foo="Hello"
#else
foo="World"
#endif
echo $foo

Now I would save this file as testScript.c and compile with gcc

gcc -E testScript.c -o testScript.sh -DHELLO

And now I have testScript.sh with me. If I run this script I get the result as

sh testScript.sh
Output: Hello

Upvotes: 3

Views: 6386

Answers (3)

Kaz
Kaz

Reputation: 58617

The problem with using that preprocessor specifically is that shell scripts use # as a comment character. So you cannot use it to preprocess shell scripts which have comments.

That may be fine if you you can stick to a coding convention that your preprocessed shell scripts use only /* ... */ and // comments, and the output has no comments.

A comment that cannot be removed is the hash-bang line. If you pass code with the hash bang line to the GNU C preprocessor, it will complain about an invalid preprocessing directive.

A solution to these problems may be to adopt a convention such as the following:

cpp -E ... -DHASH='#'

That is to say, assume there is a predefined macro called HASH which expands to the hash mark. Then in the script you can do:

HASH!/bin/sh

and also encode comments like this:

HASH This is a comment

Unfortunately, this doesn't quite work, because cpp inserts whitespace at the start of the line. I get the output:

 #!/bin/sh
^ space here, oops!

 # this is a comment

So that has to be addressed. Another problem is that there is extra verbiage in the output like this:

# 1 "prepro.sh.in"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "prepro.sh.in"

That has to be cleaned out. Here is something that points toward a viable solution. My input prepro.sh.in input file is this:

HASH!/bin/sh

HASH this is a comment

foo()
{
}

The command I'm running is this:

cpp -E -DHASH='#' prepro.sh.in | sed -e '/^#/d;s/^ #/#/'

The output:

#!/bin/sh

# this is a comment

foo()
{
}

There may me other stumbling blocks. The C preprocessor is defined not as a textual filter but as a processing step which identifies and generates "preprocessing tokens". Even the lines which are not preprocessing directives are being tokenized.

I would be concerned about some instances of significant whitespace not being correctly preserved.

The man page for GNU cpp has some admonishing words:

   The C preprocessor is intended to be used only with C, C++, and
   Objective-C source code.  In the past, it has been abused as a general
   text processor.  It will choke on input which does not obey C's lexical
   rules.  For example, apostrophes will be interpreted as the beginning
   of character constants, and cause errors.  Also, you cannot rely on it
   preserving characteristics of the input which are not significant to
   C-family languages.  If a Makefile is preprocessed, all the hard tabs
   will be removed, and the Makefile will not work.

   Having said that, you can often get away with using cpp on things which
   are not C.  Other Algol-ish programming languages are often safe
   (Pascal, Ada, etc.) So is assembly, with caution.  -traditional-cpp
   mode preserves more white space, and is otherwise more permissive.
   Many of the problems can be avoided by writing C or C++ style comments
   instead of native language comments, and keeping macros simple.

If you're going to do this anyway, it's probably a good idea to follow the useful recommendation to use the -traditional-cpp option.

Upvotes: 0

tripleee
tripleee

Reputation: 189679

What you are proposing is possible, but isn't usually done, because the shell itself provides far more flexible and dynamic features than the C preprocessor.

You can take action on an environment variable:

case $HELLO in
    '' ) foo="World" ;;
    * ) foo="Hello" ;;
esac
echo "$foo" # Note quoting

or even just

echo "${HELLO+Hello}${HELLO-World}"

You can specify default values:

: ${HELLO=Hello}

You can throw an error if something is unset:

: ${HELLO?Need a greeting}

In summary, unless you work in an environment where C really requires all of your attention, my simple recommendation would be to learn to use the shell.

Upvotes: 5

Vality
Vality

Reputation: 6607

This should work fine, in fact you do not need to name the testscript testScript.c at all. Just keep it as testScript.sh and run:

cpp testScript.sh

to run the c preprocessor on it.

This should be more portable than running gcc directly as there are other c preprocessors available other than gcc.

Upvotes: 1

Related Questions