Village
Village

Reputation: 24383

How to randomly find and replace only one occurance of a match in a file in BASH?

I am using sed to find and replace items in a file:

sed -i "s/$pattern/$replacement/g" ./file.txt

This will replace all appearances in a file.

Is there any way to randomly replace just one of the occurrences within an entire file?

Upvotes: 2

Views: 248

Answers (3)

Miller
Miller

Reputation: 35198

The only way to do this truly randomly would be to inventory all the locations of the PATTERN first. Otherwise there is no way to give proper weighting.

The following does the replacement via a script. It only replaces the first reference of a pattern on a specific line currently:

use strict;
use warnings;
use autodie;

my $file = '...';
my $pattern = qr/.../;
my $replace = '...';

my @linenums;
local @ARGV = $file;
while (<>) {
    push @linenums, $_ if $_ =~ $pattern;
}

my $linenum = $linenums[rand @linenums];

local @ARGV = $file;
local $^I = '.bak';
while (<>) {
    s/$pattern/$replace/ if $. == $linenum;
    print;
}
# unlink "$file$^I"; # Optionally Delete backup

Addendum for PATTERN on line more than once.

If you want to allow a pattern to be on a line more than once, the following enhancement would work:

use strict;
use warnings;
use autodie;

my $file = '...';
my $pattern = qr/.../;
my $replace = '...';

my @linenums;
local @ARGV = $file;
while (<>) {
    my $count = () = $_ =~ /$pattern/g;
    push @linenums, {line => $., num => $_} for 0 .. $count - 1;
}

die "No matches found" unless @linenums;

my $pick = $linenums[rand @linenums];

local @ARGV = $file;
local $^I = '.bak';
while (<>) {
    if ($. == $pick->{line}) {
        s/(?:$pattern.*?){$pick->{num}}\K$pattern/$replace/;
    }
    print;
}
# unlink "$file$^I"; # Optionally Delete backup

Upvotes: 1

jaypal singh
jaypal singh

Reputation: 77105

Using perl, you can use rand function:

perl -lane '
    while ($n = rand @F) { 
        if ($F[$n] eq "pattern") { 
            $F[$n] = "replacement"; 
            print "@F"; 
            last 
        } 
    }
' file

As per this post, variables from the shell are available in perl's %ENV hash. With bash (and some other shells) you need to take the extra step of "exporting" your shell variable so it is visible to subprocesses.

So in your case, you'll have to do:

pattern="mypattern"
replacement="myreplacement"
export pattern
export replacement
perl -lane '
    while ($n = rand @F) { 
        if ($F[$n] eq "$ENV{pattern}") { 
            $F[$n] = "$ENV{replacement}"; 
            print "@F"; 
            last 
        } 
    }
' file

Of course, this will print to STDOUT so if you need to make in-file changes, you can either use -i command line option or redirect the output to another file.

Alternatively, if this is not part of your bash script and you want to do it in perl, then you can pass the pattern and replacement on the command line.

perl -lane '
BEGIN { ($patt, $repl) = splice @ARGV, 1 }
while ($n = rand @F) {
  if ($F[$n] eq $patt) {
    $F[$n] = $repl;
    print "@F";
    last
  }
}' file "pattern" "replacement"

This will do replacement randomly once per line. If you wish to do it once per file, please leave a comment and I will add that version.

Upvotes: 1

konsolebox
konsolebox

Reputation: 75488

Using perl:

#!/usr/bin/env perl

use List::Util qw(shuffle);
use strict;
use warnings;

my $search = shift @ARGV;
my $repl = shift @ARGV;
my @lines;
my @matches;

while (<>) {
    push(@lines, $_);
    push(@matches, $.) if /$search/;
}

my @shuffled = shuffle(@matches);
my $index = shift @shuffled;

if ($index) {
    $lines[$index - 1] =~ s/$search/$repl/;
}

print @lines;

Usage:

perl script.pl string replacement file

Upvotes: 1

Related Questions