1

Consider the following code that does a simple loop over code files:

#!/bin/bash
dir="."
find $dir -name *.cpp -o -name *.h | while read file; do
    echo "processing: "$file
    # Process file here
done

Every file start with messy includes like this

#include<iostream>
#include <vector>
#include"this_is_file1.h"
#include "this_is_file2.h"
#include "This_Is_File3.h"
#include"thisIsFile4.h"
#include "ThisIsFile5.h"
#include"dir/thisIsFile6.h"
#include "dir/ThisIsFile7.h"
#include "dir/ThisIsFile8.txx"
#include "dir/ThisIsFILe9.txx"

that I would like to transform like this (and overwrite the old file)

#include <iostream>
#include <vector>
#include "this_is_file1.h"
#include "this_is_file2.h"
#include "this_is_file3.h"
#include "this_is_file4.h"
#include "this_is_file5.h"
#include "this_is_file6.h"
#include "this_is_file7.h"
#include "this_is_file8.txx"
#include "this_is_file9.txx"

More formally:

  • There should be exactly one space between the include and the file name
  • There should not be any capital letter, and every consecutive sequence of capital letters that has been replaced should be preceded by exactly one underscore, except if it's the beginning of the file name
  • There should not be any directory name

How to do that with bash?

4
  • 2
    note that * has to be escaped when part of a name pattern in find commands and also while..read is the worst way of doing this. Feb 5, 2017 at 18:26
  • @don_crissti any better code is welcome... I am a beginner with bash...
    – Vincent
    Feb 5, 2017 at 18:33
  • bash is not a text editor
    – Jeff Schaller
    Feb 5, 2017 at 19:54
  • Do you actually need to convert "IsFILe9" to "is_file9"?
    – dhag
    Feb 5, 2017 at 21:50

3 Answers 3

4

The following sed script should do it:

s/\(#include\) *\([^ ]\+\)/\1 \2/

/^#include "/ {
    s/".*\//"/
    s/"\(.\)/"\l\1/g
    s/\([^A-Z]\)\([A-Z]\)/\1_\l\2/g
    s/_\+/_/g
}

This can be applied in place to files by running:

sed -i.bak -f fix.sed input...

(This assumes that the script above is called fix.sed, and any number of files can be given as arguments afterwards. Remove .bak if you do not want any backups created.)

The first sed replacement expression matches #include followed by a possibly empty run of spaces (\ *), and replaces that run with a single space. It also turns the rest of the line ([^ ]\+) to lowercase (using \l).

The next four sed expressions, all of which only apply to lines that start with #include ", do the following:

  1. Strip path name (everything up to and including the last slash);

  2. turn the first character between quotes to lowercase;

  3. at the start of each run of uppercase letters, insert underscore and turn first character of run to lowercase;

  4. squeeze runs of underscore possibly inserted by previous steps.

This is imperfect, but does translate your sample input as intended.

4
  • And how to replace in a file?
    – Vincent
    Feb 5, 2017 at 18:35
  • 1
    (1) The question is ambiguous; the code sample doesn’t fully cover the formal problem statement. Based on the text, I believe the OP wants to fix capitalization even in #include < fileName > lines, which your answer doesn’t do. (2) You missed the hidden outlier: the ninth file is FILe9, not File9. Your answer turns that into _fILe9 (leaving the IL capitalized). (3) Why does your first line have a backslash before the space (in \ *)? It’s not needed; ⁠ * works. (4) Why does your first line contain \l? All it does is convert the " or < to lowercase (i.e., a no-op). Feb 5, 2017 at 21:12
  • Excellent remarks, thanks! Re (2), I assumed this was an error in the question, but perhaps it isn't; I should have been more explicit about this.
    – dhag
    Feb 5, 2017 at 21:49
  • I updated the code to address points (1) (3) (4). I will wait for clarification from the asker before taking care of (2).
    – dhag
    Feb 6, 2017 at 13:51
0

You might find using a tool such as astyle formats your code neatly, and does more than just fixes up the #include lines.

Your code is fine, as-is, but if you don't have spaces in your filenames or other oddities you could just as well use find:

astyle --style=allman -A1 --indent=spaces=4  \
   --break-blocks --pad-oper --pad-header --unpad-paren \
   --max-code-length=200 \
   $(find . -name '*.cpp' -print) $(find . -name '*.h' -print)
0
perl -i.bak -pe '
   if( s!(#include) *(["<])(.*/)?!$1 $2!){  ## if is a include line
        s/(?<=[a-z])(?=[A-Z])/_/g;          ##   insert CamelCase "_"
        tr/A-Z/a-z/ }'   file               ##   lowercase ids
1
  • 1
    (1) Please explain what this does and how it answers the question.  See dhag’s answer for an example of what an explanation looks like.  (2) You seem to have overlooked or misread the statement in the question: “Every file start(s) with messy includes like this.”  Implicitly, the OP wants only the #include lines to be modified; your solution will mangle every line that contains ", <, or any capital letters. Feb 5, 2017 at 20:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .