0

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:

  • sed command
  • that does nothing if record does not exist (see dovecot passwd example)
  • that replaces in file ( if there isn't very good reason for outputing to new file and moving new file to original )

Here is /etc/dovecot/passd example:

johndoe@example.com:{CRAM-MD5}479375185777b7ba573747c111483bdd628332a70268ce283dc80bedd0f65988
janedoe@example.com:{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 johndoe@example.com:{CRAM-MD5}479375185777b7ba573747c111483bdd628332a70268ce283dc80bedd0f65988
 * if johndoe@example.com gives correct password to dovecot's doveadm pw -u johndoe@example.com -poldpassword
**/
$search  = $argv[1] .':' . exec("doveadm pw -u " . $argv[1] . " -p" .  $argv[2]);

/**
 * $replace generates replace value johndoe@example.com:{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;
Miloš Đakonović
  • 3,751
  • 5
  • 35
  • 55
  • See: [how to replace a particular line in a text file using php?](http://stackoverflow.com/q/3004041/3776858) – Cyrus Mar 28 '16 at 10:20
  • One reason to avoid sed's "in-file" support is that it isn't really in-file; sed mimics in-file behaviour using a temp file, so your only benefit is simplified code, while you increase risk of problems if the filesystem fills up, or perhaps with other conditions as well. I like to write scripts with error handling on almost every command, so that if something fails, I can take appropriate action within the script. – ghoti Mar 28 '16 at 11:37
  • @ghoti good point. I was suspecting that actual replacement is not done in file itself, but anyway I'm after solution as simple as is can be since I'm not master of BASH language. – Miloš Đakonović Mar 28 '16 at 12:02

2 Answers2

2

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.
Steve Amerige
  • 1,309
  • 1
  • 12
  • 28
  • For reference, your `sed` and `stat` command lines works in Linux (with GNU versions), but not in FreeBSD or OSX. This is a good answer, but OS-specific parts of an answer should always be highlighted as such if the question isn't restricted to a particular OS. – ghoti Mar 28 '16 at 11:31
1

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

a="johndoe@example.com"
md5="abc123"
sed -i 's/^'"$a"'.*/'"$a"':{CRAM-MD5}'"$md5"'/' /etc/dovecot/passd

Output in file /etc/dovecot/passd:

johndoe@example.com:{CRAM-MD5}abc123
janedoe@example.com:{CRAM-MD5}10b7176649d8bb3eb38f6b68a0c6c826ffab5656181821e598296ebd31e8f64f
Cyrus
  • 84,225
  • 14
  • 89
  • 153