Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I have numerous HTML files all nested inside different folders contained in a single overall folder. In each of these HTML files I need to replace

/contact/index.html

With

/contact/index.php

Is there an easy way of doing this from the command line?

share|improve this question
    
This kind of job is where Perl shines. –  Thorbjørn Ravn Andersen Sep 1 '13 at 22:23
add comment

2 Answers

up vote 5 down vote accepted

Yup, if you have GNU find and GNU sed, try this in the parent folder:

find . -type f \( -iname "*.htm" -o -iname "*.html" \) -exec sed -i.bak 's#/contact/index\.html#/contact/index.php#' '{}' +

This will find all files whose name ends in .html or .HTML or .htm or .HTM (or .HtM...) and run this sed command on them:

sed -i.bak 's#/contact/index\.html#/contact/index.php#g'

This will make the substitution you want and create a backup of the original foo.htm called foo.htm.bak. If you don't want the backups, just remove .bak.


DETAILS:

The find command, obviously, finds files or folders. It's syntax can be quite complex and is explained in detail in its man page some of which is reproduced below:

The general format is find [where] [what]. In the example I have given above, the where is . which means the current directory. The what is all files that have a html or similar extension, so I use iname which is:

   -iname pattern
          Like -name, but the match is case insensitive.
          For example,  the  patterns  `fo*'  and  `F??'
          match  the  file  names  `Foo',  `FOO', `foo',
          `fOo', etc.   

However, I want it to match both html and htm so I use the -o flag which means:

  expr1 -o expr2
          Or; expr2 is not evaluated if expr1 is true.

Such constructs need to be grouped together which is done by parentheses ( ) which, however, need to be escaped from the shell so we use \( and \).

The magic happens in the -exec part:

   -exec command ;
          Execute command; true if 0 status is returned.
          All following arguments to find are  taken  to
          be  arguments to the command until an argument
          consisting of `;' is encountered.  The  string
          `{}'  is  replaced  by  the  current file name
          being processed everywhere it  occurs  in  the
          arguments  to  the  command, not just in argu‐
          ments where it is alone, as in  some  versions
          of  find.   [...] The specified command is
          run once for each matched file.   The  command
          is executed in the starting directory.   There
          are unavoidable security problems  surrounding
          use  of  the  -exec action; you should use the
          -execdir option instead.

In other words, given a command like -exec ls {},find will find all files matching the conditions you have set and iterate through them, replacing {} with the current file name and executing the command given. I am also using + instead of \; to end the exec call because that will cause find to try and run as few commands as possible, this is just a minor optimization unless you have thousands of files when it could be important:

   -exec command {} +
          This variant of  the  -exec  action  runs  the
          specified  command  on the selected files, but
          the command line is built  by  appending  each
          selected  file name at the end; the total num‐
          ber of invocations of the command will be much
          less  than  the  number of matched files.  The
          command line is built in  much  the  same  way
          that xargs builds its command lines.  Only one
          instance of `{}' is allowed  within  the  com‐
          mand.  The command is executed in the starting
          directory.

Finally, sed is a command line text stream editor, it will apply the command you give it to each line of a file. In this case, the command is a substitution, the basic format is:

s#pattern#replacement#flags

The delimiters (# ) can be any special character and are traditionally / but I chose # because otherwise I would have had to escape the /. Note that ChrisDown in his answer chose to use |. This is simply a personal choice and the two are equivalent.

share|improve this answer
    
Sounds good I'll try it in a second. Could I be rude and ask for a break down of each bit of the command you've presented? I much prefer learning than copy and pasting. Thanks. –  Ben Sep 1 '13 at 18:36
    
@Ben sure, have a look at the updated answer. –  terdon Sep 1 '13 at 18:50
    
Beautiful I shall read through it now, thanks. –  Ben Sep 1 '13 at 18:58
add comment

Assuming that you have GNU sed:

find -iname '*.html' -type f -exec sed -i 's|/contact/index\.html|/contact/index.php|g' {} +
share|improve this answer
    
Oy! You edited mine while I was doing exactly the same correction (escaping '.' as you very correctly did in your answer) :) –  terdon Sep 1 '13 at 18:16
    
@terdon Sorry! Would you prefer I left a comment instead in future? I'm never sure what's the etiquette with just editing, or asking instead, but you seemed to appreciate it before, so I dunno. –  Chris Down Sep 1 '13 at 18:16
    
Good heavens no, please edit and correct by all means! It was just funny that I noticed you did that, thought it was a good idea and was about to correct it and you beat me to it. It did not bother me in the least, on the contrary I appreciate it. –  terdon Sep 1 '13 at 18:17
    
@ChrisDown - yes always edit anything I write too. When I've asked Gilles & Stephane they've said the same. Most of the regulars are fine with editing. Some of the newer people seem to get put off by it, I usually explain to them that they can always revert what they don't like 8-). –  slm Sep 1 '13 at 19:11
    
@slm Stephane always edits mine when I forget -- (ie. every single time, for some reason, even though it's the thing I always tell people to do...). Please also feel free to edit mine. –  Chris Down Sep 1 '13 at 19:24
show 3 more comments

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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