KeithSmith
KeithSmith

Reputation: 774

Changing #include filenames to match case

I have a body of C/C++ source code where the filename in the #include statement does not match the *.h file exactly. The match is correct, but is case insensitive. This is the type of source files that occur in a Windows system.

I want to change all the source files so that all #include statements are exact matches to the filenames they refer to.

All filenames to change are enclosed in quotes.

Example:

List of files

File1.h
FILE2.H
file1.cpp

file1.cpp

#include "file1.h"
#include "file2.h"

Change file1.cpp to

#include "File1.h"
#include "FILE2.H"

I would like to create an automated script to perform this update.

I have listed steps below that are pieces of this process, but I can't seem to bring the pieces together.

  1. Create a list of all *.h files, ls *.h > include.lst. This creates a file of all the filenames with the correct case.
  2. Using the filenames in include.lst create a sed command 's/<filename>/<filename>/I' which does a case insensitive search and replaces the match with properly cased filename. I believe I only have to do the replacement once, but adding the global g will take care of multiple occurances.
  3. Apply this list of replacements to all files in a directory.

I would like suggestions on how to create the sed command 2) given include.lst. I think I can handle the rest.

Upvotes: 2

Views: 2544

Answers (5)

nyanpasu64
nyanpasu64

Reputation: 3145

My solution doesn't fail for pathnames containing slashes (hopefully you don't contain % signs in your header paths).

It's also orders of magnitude faster (takes ~13 seconds on a few hundred files, as opposed to several minutes of waiting).

#!/bin/bash

shopt -s globstar failglob nocaseglob

# You should pushd to your include path-root.
pushd include/path/root
headers=( **/*.h )
popd
headers+=( *.h )    # My codebase has some extra header files in the project root.

echo ${#headers[*]} headers

# Separate each replacement with ;
regex=""
for header in "${headers[@]}"; do
   regex+=';s%#include "'"$header"'"%#include "'"$header"'"%gI'
done
regex="${regex:1}"

find . -type f -iname '*.cpp' -print0 | \
    xargs -0 sed -i "$regex"

It's much faster to make sed run just once per file (with many ;-separated regexes).

Upvotes: 0

KeithSmith
KeithSmith

Reputation: 774

Thanks for all the details on lowercasing filenames and #include strings. However, my original question was to perform a literal replacement.

Below is the basic command and sed script that met my requirements.

ls *.h *.H | sed -e "s/\([^\r\n]*\)/s\/\\\(\\\#include\\\s\\\"\\\)\1\\\"\/\\\1\1\\\"\/gi/g" >> sedcmd.txt

  1. ls *.h *.H creates a list of files, one line at a time
  2. Pipe this list to sed.
  3. Search for the whole line, which is a filename. Put the whole line in group 1. s/\(^\r\n]*\)/
  4. Replace the whole line, the filename, with the string s/\(\#include\s"\)<filename>"/\1<filename>"/gi

The string #include<space>" is placed in group 1. The i in the gi states to do a case insensitive search. The g is the normal global search and replace.

Given a filename ACCESS.H and cancel.h, the output of the script is

s/\(\#include\s"\)ACCESS.H"/\1ACCESS.H"/gi
s/\(\#include\s"\)cancel.h"/\1cancel.h"/gi

Finally, the sed command file can be used with the command

sed -i.bak -f sedcmd.txt *.cpp *.h

Upvotes: 0

potong
potong

Reputation: 58438

This might work for you (GNU sed):

sed 's|.*|s/^#include "&"$/#include "&"/i|' list_of_files | sed -i -f - *.{cpp,h} 

Upvotes: 0

SwiftMango
SwiftMango

Reputation: 15294

  1. Use sed in script, or use Perl script:

    find . -name *.c -print0 | xargs -0 sed -i.bak -e "s/\#include\s\"\([^\"]+/)\"/\#include\s\"\L\1\"/"
    

    -i.bak will back up the file to original_file_name.bak so you do not need to worry if you mess up

    This line changes all header includes to lower case in your C files.

  2. Then you want to change all files names:

    find . -name *.h -print0 | xargs -0 rename 's/(*)/\L\1/'
    

    This renames all header file to lower case.

This is for linux only. If you are using Windows, you might want to use Perl or Python script for all above.

Upvotes: 2

Lev Levitsky
Lev Levitsky

Reputation: 65791

for hfile in $(find /header/dir -type f -iname '*.h'); do
    sed -i 's/#include "'$hfile'"/#include "'$hfile'"/gI' file1.cpp
done

I hope I got the quotes right :) Try without -i before applying.

You can wrap the sed call in another loop like this:

for hfile in $(find /header/dir -type f -iname '*.h'); do
    for sfile in $(find /source/dir -type f -iname '*.cpp'); do
        sed -i 's/#include "'$hfile'"/#include "'$hfile'"/gI' "$sfile"
    done
done

Upvotes: 2

Related Questions