【问题标题】:Sed search and replace in file, only if pattern exists仅当模式存在时,Sed 在文件中搜索和替换
【发布时间】:2016-03-28 19:19:47
【问题描述】:

我想用另一个替换文件中的一个字符串,更具体地说,替换 dovecot passwd 文件 (/etc/dovecot/passwd) 中的 username:password 条目,但前提是记录存在。

那么,我需要什么:

  • sed 命令
  • 如果记录不存在则不执行任何操作(参见 dovecot passwd 示例)
  • 在文件中替换(如果没有很好的理由输出到新文件并将新文件移动到原始文件)

这里是/etc/dovecot/passd 示例:

johndoe@example.com:{CRAM-MD5}479375185777b7ba573747c111483bdd628332a70268ce283dc80bedd0f65988
janedoe@example.com:{CRAM-MD5}10b7176649d8bb3eb38f6b68a0c6c826ffab5656181821e598296ebd31e8f64f

以及到目前为止我尝试过的内容(创建新文件,输出到新文件并将 mv 输出到原始文件即使记录不存在

#!/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;

【问题讨论】:

  • 避免 sed 的“文件内”支持的一个原因是它不是真正的文件内; sed 使用临时文件模拟文件内行为,因此您唯一的好处是简化代码,而如果文件系统填满,或者可能还有其他情况,您会增加出现问题的风险。我喜欢编写几乎对每个命令都有错误处理的脚本,这样如果出现故障,我可以在脚本中采取适当的措施。
  • @ghoti 好点。我怀疑实际替换不是在文件本身中完成的,但无论如何我都在寻求尽可能简单的解决方案,因为我不是 BASH 语言的主人。

标签: bash sed


【解决方案1】:

虽然您可以使用 sed 通过 -i 标志进行替换,但文件的最后修改时间将始终更新:

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

确实,请注意,文件的诞生时间、最后访问时间以及最后修改时间将使用上面的命令进行更新。这可以通过在上述 sed 命令之前和之后执行以下命令来显示:

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

此外,无论是否发生替换,上述 sed 命令都将返回 0。

所以,如果你绝对不能更新文件,包括时间戳,你可以这样做:

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

如果您需要显示文件已更新的消息,您可以使用list

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

最后,如果您还希望在密码未更改时显示一条消息,您可以这样做:

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

要将上述作为单行执行,请务必在列表中添加分号,包括最后一个元素:

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

一些注意事项:

  • 只需一个替换,sed 的 -e 标志是可选的
  • 我在 "$passwd_file" 周围添加了引号,以防该文件中有空格。即使没有,这样做也是一个好习惯。
  • 我已将 -e 标志添加到 echo。虽然这适用于 bash,但如果您的 shell 不是 bash,则可能不支持。您上面的 echo 语句旨在输出 2 个换行符。 -n 标志到 echo 抑制终端换行符。如果您希望对格式进行更多控制,如上面最后一个示例所示,您可以考虑使用比 echo 更一致的行为的 printf 命令。

【讨论】:

  • 作为参考,您的 sedstat 命令行在 Linux(使用 GNU 版本)中有效,但在 FreeBSD 或 OSX 中无效。这是一个很好的答案,但如果问题不限于特定操作系统,则应始终突出显示答案中特定于操作系统的部分。
【解决方案2】:

使用 GNU sed 来“就地”编辑您的文件:

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

在文件 /etc/dovecot/passd 中输出:

johndoe@example.com:{CRAM-MD5}abc123 janedoe@example.com:{CRAM-MD5}10b7176649d8bb3eb38f6b68a0c6c826ffab5656181821e598296ebd31e8f64f

【讨论】:

    猜你喜欢
    • 2011-08-16
    • 1970-01-01
    • 2017-12-30
    • 2016-09-06
    • 2014-03-14
    • 2016-06-21
    • 2014-09-13
    • 2011-09-01
    相关资源
    最近更新 更多