Miloš Đakonović
Miloš Đakonović

Reputation: 3871

Sed search and replace in file, only if pattern exists

I'd like to replace one string in file with another, to be more specific, replace username:password entry in dovecot passwd file ( /etc/dovecot/passwd ) but only if record exists.

So, what I need:

Here is /etc/dovecot/passd example:

[email protected]:{CRAM-MD5}479375185777b7ba573747c111483bdd628332a70268ce283dc80bedd0f65988
[email protected]:{CRAM-MD5}10b7176649d8bb3eb38f6b68a0c6c826ffab5656181821e598296ebd31e8f64f

and what I've tried so far (creates new file, output to new and mv to original even if record does not exist)

#!/usr/bin/env php
<?php

$passwd_file = '/etc/dovecot/passwd';
$time = time();
$newtempfile = '/tmp/' . $time . 'dvctpwd';

/**
 * $search generates replace target [email protected]:{CRAM-MD5}479375185777b7ba573747c111483bdd628332a70268ce283dc80bedd0f65988
 * if [email protected] gives correct password to dovecot's doveadm pw -u [email protected] -poldpassword
**/
$search  = $argv[1] .':' . exec("doveadm pw -u " . $argv[1] . " -p" .  $argv[2]);

/**
 * $replace generates replace value [email protected]:{CRAM-MD5}...
 * i.e. scheme with new password
**/
$replace = $argv[1] .':' . exec("doveadm pw -u " . $argv[1] . " -p" .  $argv[3]);

$command = <<<DD
sed -e "s|$search|$replace|" $passwd_file > $newtempfile && mv $newtempfile $passwd_file && echo "Password changed!\n";
DD;

$output = passthru($command);
echo $output . PHP_EOL;

Upvotes: 0

Views: 2631

Answers (2)

Cyrus
Cyrus

Reputation: 88563

With GNU sed to edit your file "in place":

a="[email protected]"
md5="abc123"
sed -i 's/^'"$a"'.*/'"$a"':{CRAM-MD5}'"$md5"'/' /etc/dovecot/passd

Output in file /etc/dovecot/passd:

[email protected]:{CRAM-MD5}abc123
[email protected]:{CRAM-MD5}10b7176649d8bb3eb38f6b68a0c6c826ffab5656181821e598296ebd31e8f64f

Upvotes: 1

Steve Amerige
Steve Amerige

Reputation: 1499

While you can use sed to make substitutions in place with the -i flag, the last-modified time of the file will always be updated:

sed -i "s|$search|$replace|" "$passwd_file"

Indeed, note that the time of file birth, time of last access, as well as time of last modification will be updated with the command above. This can be shown by doing the following command both before and after the above sed command:

stat -c "%W %X %Y %Z" "$passwd_file"

Also, the above sed command will return 0 whether or not a replacement happens.

So, if you absolutely must not update the file at all, including timestamps, you can do:

grep -q "$search" "$passwd_file" &&
sed -i "s|$search|$replace|" "$passwd_file"

And if you need to show a message that the file has been updated, you can use a list:

grep -q "$search" "$passwd_file" &&
{
   sed -i "s|$search|$replace|" "$passwd_file"
   echo -e "Password changed!\n"
}

Finally, if you wish to also have a message that is shown if the password is not changed, you can do:

grep -q "$search" "$passwd_file" &&
{
   sed -i "s|$search|$replace|" "$passwd_file"
   printf "Password changed!\n\n"
} || printf "Password not changed!\n\n"

To do the above as a single line, be sure to add semicolons in the list, including the last element:

grep -q "$search" "$passwd_file" && { sed -i "s|$search|$replace|" "$passwd_file"; printf "Password changed!\n\n"; } || printf "Password not changed!\n\n"

Some notes:

  • With just a single substitution, the -e flag to sed is optional
  • I've added quotes around "$passwd_file" in case that file ever has spaces in it. Even if it doesn't, it's a good practice to do this.
  • I've added the -e flag to echo. While this is valid for use with bash, if your shell is not bash, then this might not be supported. Your echo statement above is intended to put out 2 newlines. The -n flag to echo suppresses the terminal newline. You might consider the printf command that has more consistent behavior than echo if you wish to have more control of formatting as is shown in the final example above.

Upvotes: 2

Related Questions