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
?

- 125,559
- 25
- 270
- 266

- 371
- 1
- 3
- 7
-
1Very similar case: http://unix.stackexchange.com/questions/20577/unix-add-a-tag-to-the-beginning-of-each-file – pbm Sep 13 '11 at 17:33
-
Related (has some Python code examples): language agnostic - Tool for adding license headers to source files? - Stack Overflow – sdaau Jul 04 '14 at 15:11
7 Answers
shopt -s globstar
for f in **/*.cpp; do
cat header_file "$f" > "$f.new" && mv "$f.new" "$f"
done
Notes:
- This assumes that you have full (read+write+execute) access to the directory tree.
- All
.cpp
files will be re-created, owned by you and with default permissions. Hard links (if any) will be broken.

- 1,173
- 1
- 9
- 14
-
1It should be mentioned that you need to turn on
globstar
in bash for this to work. – Chris Down Sep 15 '11 at 09: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
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

- 32,916
-
-
-
@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 09: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
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 07:56 -
1Some advices: remove parentheses around
grep
, add-q
option togrep
. Always add double quotes around$f
. – enzotib Sep 15 '11 at 08:04 -
1Quotes 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
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

- 125,559
- 25
- 270
- 266
-
-
@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 04 '17 at 20:37
#!/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

- 131
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 \{}' \;

- 231
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 {} \;

- 101