I have a bunch of files (45, so too many to edit them manually without going crazy) which all require the same changes (as seen in the three diffs at the end of this question).

Most tools used for search-and-replace on the commandline only support line-by-line replacements so they won't work.

Is there some commandline tool available which can do the job? Basically performing a multi-line regex search&replace with backreferences without shoving the whole regex into a single line (i.e. it should be read from a file or stdin) should be enough, but maybe there's an even better solution to do this...

--- a/editfile.html
+++ b/editfile.html
@@ -60,7 +60,5 @@

 <script type="text/javascript">
-    $(document).ready(function() {
-        editFileInit(opts);
-    });
+    initPage('editFile', opts);
 {% endblock %}
diff --git a/gallery.html b/gallery.html
index 4a07f70..81084ad 100644
--- a/exodus/templates/gallery.html
+++ b/exodus/templates/gallery.html
@@ -87,9 +87,7 @@

     <script type="text/javascript">
-        $(document).ready(function() {
-            galleryInit({
-                editTitleUrl: {{ csrf_url_for('gallery_editpictitle')|tojson|safe }},
-                delPicUrl: {{ csrf_url_for('gallery_delpic')|tojson|safe }}
-            });
+        initPage('gallery', {
+            editTitleUrl: {{ csrf_url_for('gallery_editpictitle')|tojson|safe }},
+            delPicUrl: {{ csrf_url_for('gallery_delpic')|tojson|safe }}
diff --git a/stream_history.html b/stream_history.html
index 783fea4..ea837fa 100644
--- a/exodus/templates/stream_history.html
+++ b/exodus/templates/stream_history.html
@@ -25,8 +25,6 @@

     <script type="text/javascript">
-        $(document).ready(function() {
-            songHistoryInit({
-                dataURL: {{ url_for('stream_history_json')|tojson|safe }}
-            });
+        initPage('songHistory', {
+            dataURL: {{ url_for('stream_history_json')|tojson|safe }}

If the indentation is not preserved it's not a big issue.

  • 2
    You've got diffs already. Then the most obvious thing to do is to run patch to patch the files using the diffs. Is this not what you intend to do? If not, please try to explain what you need in other words. – gertvdijk Sep 13 '12 at 22:02
  • I have many more files where the same kind of change needs to be applied - those three were done manually to demonstrate what I intend to do. – ThiefMaster Sep 13 '12 at 22:03
  • 1
    This begs for emacs/vim macros – Michael Mrozek Sep 13 '12 at 23:18
  • Can you point out which parts are variables that you are wanting to capture with backreferences? e.g. in your first example, what are the rules (if any) for constructing the replacement? – ire_and_curses Sep 14 '12 at 0:16
  • Turn fooInit({...}) into initPage('foo', {...}) and possibly get rid of the $(document).ready(function(){...}); around it – ThiefMaster Sep 14 '12 at 0:23

Let's keep it simple. The files fit comfortably in memory, so don't bother reading them line by line or anything like that. Perl's -p (iterate over file contents) switch and -0777 “slurp mode” (read a whole file at once, not line by line) are convenient.

To act on multiple files, it's convenient to use -i (edit files in place).

To perform the substitution, use the s substitution operator. See perlre#Modifiers for an explanation of the modifiers msx.

perl -0777 -i.orig -p -e '
      ^(\s+)                                  # indentation
      \Q$(document).ready(function() {\E\n
      \s*(\w+)Init                            # function name
      (\(((?:(?>[^()]+)|(?3))*)\))            # balanced parentheses
       ${1}initPage(\x27$2\x27, $3);
' **/*.js

**/*.js matches all .js files in the current directory and its subdirectories recursively. This works out of the box in zsh. If you use bash, add shopt -s globstar to your ~/.bashrc.

(Untested, you'll probably have to tweak the regexp a bit.)


Dumps output to stdout, so won't trash your files in place.


use strict;
use warnings;

# Slurp our input...
my $text = do {local $/; <> };

# Extract the varying parts: foo and bar, for fooInit(bar)
my ($init_prefix, $init_opts) = $text =~ m/([a-zA-Z0-9]+)Init\(([^;]+;)/m;

# Construct the replacement: fooInit(bar) -> initPage('foo', bar)
my $replace_with = "initPage('$init_prefix', $init_opts";

# Target the text to replace...
my $to_replace = '\$\(document.*;';

# Perform the replacement;
$text =~ s/$to_replace/$replace_with/gs;

print $text;

Tested for all three of your examples. Indentation is not correct for the replacement lines, although the code is correct, and the first line of the replacement will start in the right place.

$ perl replace.pl 1.js

script type="text/javascript">
    initPage('editFile', opts);
{% endblock %}

$ perl replace.pl 2.js

 <script type="text/javascript">
    initPage('gallery', {
            editTitleUrl: {{ csrf_url_for('gallery_editpictitle')|tojson|safe }},
            delPicUrl: {{ csrf_url_for('gallery_delpic')|tojson|safe }}

$ perl replace.pl 3.js

 <script type="text/javascript">
    initPage('songHistory', {
            dataURL: {{ url_for('stream_history_json')|tojson|safe }}

Anything you can do with :ex mode in vi can be turned into a script for ed

ed is like vi but without all that wimpy annoying user-friendly interface getting in your way and slowing you down.

All joking aside, ed is a useful tool for the scripted editing of text files.

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.