19

I'm trying to add a license header to all header files and source files in a project directory using a for loop. This is not working, is there any other approach using sed?

14
for f in **/*.cpp; do
  cat header_file $f > $f.new
  mv $f.new $f
done
  • 1
    It should be mentioned that you need to turn on globstar in bash for this to work. – Chris Down Sep 15 '11 at 9:44
  • And that you need bash > 4.0 (e.g., Mac OS X and RedHat 5 are still on 3.X) – Matteo Sep 16 '11 at 17:32
11

This is more or less just an extended commentary on Daniel Serodio'` answer. I started writitng it as a comment, but it quickly grew too large...

For a bash glob to be recursive, it requires shopt -s globstar. You must enable globstar, otherwise ** doesn't work. The globstar shell option was introduced to version 4 of bash.

To avoid processing a directory such as my.cpp/, use the test [[ -f $f ]]... When the test is in double square-brackets, variables don't need to be double quoted.

You can also consider the possibility of there being no matching files by using shopt -s nullglob, which allows patterns which match no files to expand to a null string, rather than themselves.

To handle multiple patterns, you can chain the glob patterns: **/*.cpp **/*.h, but perhaps preferrably, when the shell option extglob is on via shopt -s extglob, you can use such constructs such as **/*.@(cpp|h) which avoids multiple passes over the file system; once for each pattern.

If you want .files to be included, use .*.cpp etc, or use shopt -s dotglob

To safely handle modifying a file which is being piped, use sponge from package moreutils (it saves you the need to create your own temp file)


printf "// The License\n\n" > /tmp/$USER-license

shopt -s globstar nullglob extglob
for f in **/*.@(cpp|h) ;do
  [[ -f $f ]] && cat "/tmp/$USER-license" "$f" | sponge "$f"
done
  • Much more complete and informative answer than the other one, +1 – n0pe Sep 14 '11 at 2:05
  • +1, but [[ can gracefully handle a null $f. – enzotib Sep 14 '11 at 7:23
  • @enzotib. thanks. I'm not sure were I got that idea from. It must have been from how the single square-bracket works (doesn't)... eg unset x; [ -f $x ] && echo exists reports "exists" ... (fixed) – Peter.O Sep 14 '11 at 9:12
  • Since version 4 of bash is not mainstream yet I would substitute the **/*.@(cpp|h) with $( find . -name "*.h" -name "*.cpp") – Matteo Sep 16 '11 at 17:35
3

Thank you @fred, @maxmackie, @enzotib.

Can you please check the procedure I have followed.

#!/bin/sh
# script to copy the headers to all the source files and header files
for f in *.cpp; do
  if (grep Copyright $f);then 
    echo "No need to copy the License Header to $f"
  else
    cat license.txt $f > $f.new
    mv $f.new $f
    echo "License Header copied to $f"
  fi 
done   

otherwise the license header will be copied to multiple number of times.

Please suggest me a pattern to go through all the headers and sources in project directory and subdirectories.

I could not understand fully what @fred has suggested.

  • In general, your code looks okay. I would be more specific about identifying the exact location of "Copyright", eg. specify the line number and position on that line. You can prevent sed reading the entire file by quitting on the line where you expect to find "Copyright"... eg. targln=2; findln=$(sed -rne $targln'{\|// Copyright|=;q}' "$f"); if ((findln==targln));then ... but, of course, above and beyond everything else, thoroughly test it first... PS. It is par for the course here at Unix & Linux to post such extras in your original question, not as an answer... – Peter.O Sep 15 '11 at 7:56
  • 1
    Some advices: remove parentheses around grep, add -q option to grep. Always add double quotes around $f. – enzotib Sep 15 '11 at 8:04
  • 1
    Quotes are vital, otherwise you will be likely bitten by word splitting. As a basic rule, quote every expansion unless you know that word splitting or other interpretation will not be performed by the shell. – Chris Down Sep 15 '11 at 12:07
3

You can do this with ex or ed if you prefer (you should not do this with sed as you requested, sed is designed to edit streams, -i is a bad idea for a variety of reasons):

shopt -s globstar

for _file in **/*.@(cpp|h); do
    ed -s "${_file}" << EOF
0a
/* This file is licensed under the foo license.
   All copyright strictly enforced by the copyright monster. */
.
w
EOF
    done
  • Why is sed -i a bad idea? – Daniel Serodio Jan 4 '17 at 20:14
  • @DanielSerodio By default, sed -i breaks symlinks and hardlinks, resulting in unexpected behaviour. At best it is non-intuitive, at worse it is actively harmful. – Chris Down Jan 4 '17 at 20:37
2

If you have a header_file with desired contents:

find -name '*.cpp' -exec bash -c 'cat header_file \{} > \{}.new; mv \{}.new  \{}' \;
find -name '*.h' -exec bash -c 'cat header_file \{} > \{}.new; mv \{}.new  \{}' \;
1
#!/bin/bash

for i in `find . -name '*.[m|h]'` # or whatever other pattern...
do
  echo $i
  if ! grep -q Copyright $i
  then
    cat copyright.txt $i >$i.new && mv $i.new $i
  fi
done
enter code here
0

Gerhard Gappmeier's blog - Howto recursively replace file headers of source files

The blog post has attached a tar.gz file, containing the files needed.

It has a header.template file, where you can write a custom comment, it can span multiple lines.

It has an remove_header.awk script that replaces existing headers with the new header.

In order to run it:

find . -name "*.h" -exec ~/rh/replace_header.sh {} \;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.