views:

543

answers:

5

I have a project where I have folders, subfolders, and files. I need to replace the word Masi by the word Bond in each files.

I run the following Sed script called replace unsuccessfully

s/Masi/Bond/

in Zsh by

sed -f PATH/replace PATH2/project/**

It gives me all files, also the ones which do not have Masi, as an output.

Sed is not necessarily the best tool for the task. I am interested in Python and Perl.

How would you do the replacement in Sed/Perl/Python, such that only the file contents are changed?

+3  A: 

Why not just pass the -i option (man sed) to sed and be done with it? If it doesn't find Masi in a file, the file will just be rewritten with no modification. Or am I missing something?

If you don't want to replace the files' contents inline (which is what the -i will do) you can do exactly as you are now, but throw a grep & xargs in front of it:

grep -rl Masi PATH/project/* | xargs sed -f PATH/replace

Lots of options, but do not write an entire perl script for this (I'll give the one-liner a pass ;)). find, grep, sed, xargs, etc. will always be more flexible, IMHO.

In response to comment:

grep -rl Masi PATH/project/* | xargs sed -n -e '/Masi/ p'
Sean Bright
@Sean: How can you see which lines are going to be changed before you run the command?
Masi
I've updated my answer to address your comment.
Sean Bright
@Sean: Is your solution safe in critical environments, similarly as http://stackoverflow.com/questions/894802/unable-to-replace-the-word-in-a-given-folders-contents-by-sed-python-perl/894982#894982 ? I am not sure how safe it is to combine the commands.
Masi
+12  A: 

To replace the word in all files found in the current directory and subdirectories

perl -p -i -e 's/Masi/Bond/g' $(grep -rl Masi *)

The above won't work if you have spaces in filenames. Safer to do:

find . -type f -exec perl -p -i -e 's/Masi/Bond/g' {} \;

or in Mac which has spaces in filenames

find . -type f -print0 | xargs -0 perl -p -i -e 's/Masi/Bond/g'

Explanations

  • -p means print or die
  • -i means "do not make any backup files"
  • -e allows you to run perl code in command line
Pete TerMaat
+1 for an excellent grep trick to only modify the files that need changing.
Chris Lutz
+1 You beat me to it. This is my favorite perl one liner. The question is: after using this one liner to fix up hundreds of files in a few seconds, do you continue working, or do you take the rest of the day because that's how long it would take someone with notepad?
Mark Beckwith
-i means that case does not matter. What are the other options -p and -e? I did not find explations for them at man perl.
Masi
@Pete: What should be changed in the code to search only the current directory?
Masi
@Masi: no; -i means in-place alter with no backup files. You could use, for example, '-i.bak' to have Perl create a filename.bak backup file of each modified file.
Jonathan Leffler
@Masi: remove the 'r' (recursive) from 'grep -r' in the first command to limit it to the current directory.
Pete TerMaat
@Masi: Try 'man perlrun' instead of 'man perl' to get details on the p, i, and e flags: http://perldoc.perl.org/perlrun.html
Pete TerMaat
I believe "print0 | xargs -0" is only supported on linux so option 2 is the best for unix. Don't know about Windows/Cygwin.
jiggy
A: 

A solution tested on Windows

Requires CPAN module File::Slurp. Will work with standard Unix shell wildcards. Like ./replace.pl PATH/replace.txt PATH2/replace*

#!/usr/bin/perl

use strict;
use warnings;
use File::Glob ':glob';
use File::Slurp;
foreach my $dir (@ARGV) {
  my @filelist = bsd_glob($dir);
  foreach my $file (@filelist) {
    next if -d $file;
    my $c=read_file($file);
    if ($c=~s/Masi/Bond/g) {
      print "replaced in $file\n";
      write_file($file,$c);
    } else {
      print "no match in $file\n";
    }
  }
}
Alexandr Ciornii
The shell wildcards will be handled by the shell. Handling them in Perl is unnecessary.
Chris Lutz
Depends on OS :) - I'm testing this on Windows.
Alexandr Ciornii
I'm with Alexandr here. I've never seen the Windows shell do wildcard expansion.
pjf
On Windows I've often done "@files = map { glob } @ARGV"
Michael Carman
+3  A: 

Renaming a folder full of files:

use warnings;
use strict;
use File::Find::Rule;

my @list = File::Find::Rule->new()->name(qr/Masi/)->file->in('./');

for( @list ){
   my $old = $_;
   my $new = $_;
   $new =~ s/Masi/Bond/g;
   rename $old , $new ;
}

Replacing Strings in Files

use warnings;
use strict;
use File::Find::Rule;
use File::Slurp;
use File::Copy;

my @list = File::Find::Rule->new()->name("*.something")->file->grep(qr/Masi/)->in('./');

for( @list ){
   my $c = read_file( $_ );
   if ( $c =~ s/Masi/Bond/g; ){
    File::Copy::copy($_, "$_.bak"); # backup.
    write_file( $_ , $c );
   }
}
  • strict (core) - Perl pragma to restrict unsafe constructs
  • warnings (core) - Perl pragma to control optional warnings
  • File::Find::Rule - Alternative interface to File::Find
  • File::Find (core) - Traverse a directory tree.
  • File::Slurp - Efficient Reading/Writing of Complete Files
  • File::Copy (core) - Copy files or filehandles
Kent Fredric
Does the last script rename all files including subdirectories? - - Chaos gave me his similar script at http://lostsouls.org/grimoire_convert_all . Your script seems to be shorter. This suggests me that it is not pure Perl. Which parts would you improve in your script to make it safe in critical environments?
Masi
@Masi , er, sure its pure perl, it just uses modules to do the heavy lifting and they do all the hard work which have bee solved for me. File::Find::Rule is perfectly safe for critical environments, possibly *more* so than DIY'ing it with manual opendir because its at least *tested* on *multiple* platforms to work as expected.
Kent Fredric
A: 
import glob
import os

# Change the glob for different filename matching 
for filename in glob.glob("*"):
  dst=filename.replace("Masi","Bond")
  os.rename(filename, dst)
Christopher