Search code examples
bashawksedsolaris-10

Solaris 10, sed insert #include <iostream> before first #include statement


On Solaris 10, I have several source files similar to file1.cpp, as given below:

// file1.cpp
#include <string>
#include <unordered_map>
void func() {
. . .
}

I would like to insert #include <iostream> before first #include (if any): thus, the file would become:

// file1.cpp
#include <iostream>
#include <string>
#include <unordered_map>
void func() {
. . .
}

In case there is no include e.g.

// file1.cpp
void func() {
. . .
}

This will become:

// file1.cpp
#include <iostream>
void func() {
. . .
}

However, if the file already contains #include <iostream>, the insertion of another #include <iostream> would be ignored. Thus, if we have,

// file1.cpp
#include <string>
#include <unordered_map>
#include <iostream>
void func() {
. . .
}

It will remain same, i.e.

// file1.cpp
#include <string>
#include <unordered_map>
#include <iostream>
void func() {
. . .
}

My attempt:

bash-3.00$ cat TRYIT
#!/bin/bash
echo \#include \<iostream\> > x$$
for  i in `find . -type f -name "*.cpp"`
do
 if  ! grep -s "\#include \<iostream\>" "$i"  > /dev/null; then
    sed  "/\#include/r x$$" "$i" > "$i".bak
    mv "$i".bak "$i"
 fi
done
rm x$$

But this does not work as per my requirement, please suggest.


Solution

  • This works OK on Linux, and I believe it should work on Solaris.

    File xyz.c:

    /* First line must not start #include */
    #include <stdio.h>
    #define PERGATORY
    #include <meniscus.h>
    
    the real code
    

    Command:

    $ sed -e '1,/^[:space:]*#[:space:]*include/ { /^[:space:]*#[:space:]*include/i\
    > #include <iostream>
    > }' xyz.c
    /* First line must not start #include */
    #include <iostream>
    #include <stdio.h>
    #define PERGATORY
    #include <meniscus.h>
    
    the real code
    $
    

    If you need to deal with files that have a #include at the start of the first line, it is fiddlier. Without a change, you get #include <iostream> inserted before line 1 (what was wanted) and also before the first line after line 1 that starts with #include. With GNU sed, you can use 0 in place of the 1 and it works well; referencing line 0 is not a POSIX standard feature and probably won't work on Solaris, though.

    I'm not sure what's the best workaround without that — probably using awk instead of sed is the least difficult (and it works nicely for the first #include after the first line as well, of course — it is a more general solution):

    awk '/^[:space:]*#[:space:]*include/ { if (done++ == 0)
                                              print "#include <iostream>" }
         {print}' …filename…
    

    What about if I want to have iostream as variable captured in $NM, i.e. NM=iostream set earlier in the script? How do I use $NM within the awk?

    NM="iostream"
    
    awk -v header="$NM" \
        '/^[:space:]*#[:space:]*include/ { if (done++ == 0)
                                              printf "#include <%s>\n", header }
         {print}' …filename…
    

    The key point is the use of -v variable=value on the command line to relay the header name to the awk script, and then modifying the print into a printf to format the string correctly (remembering that a newline is needed).