【问题标题】:sed beginner: changing all occurrences in a foldersed初学者:更改文件夹中的所有事件
【发布时间】:2010-10-28 15:44:02
【问题描述】:

我需要对文件夹(及其子文件夹)中的所有文件进行正则表达式查找和替换。执行此操作的 linux shell 命令是什么?

例如,我想对所有文件运行此命令,并用新的替换文本覆盖旧文件。

sed 's/old text/new text/g' 

【问题讨论】:

标签: regex linux shell sed


【解决方案1】:

仅使用 sed 是无法做到的。您至少需要同时使用 find 实用程序:

find . -type f -exec sed -i.bak "s/foo/bar/g" {} \;

此命令将为每个更改的文件创建一个.bak 文件。

注意事项:

  • sed 命令的 -i 参数是 GNU 扩展,因此,如果您使用 BSD 的 sed 运行此命令,则需要将输出重定向到新文件,然后重命名。
  • find 实用程序未在旧 UNIX 框中实现 -exec 参数,因此,您需要改用 | xargs

【讨论】:

  • \; 是干什么用的?
  • 我们需要知道参数 -exec 的命令以“;”结尾的位置。但是 shell 使用相同的符号 (;) 作为 shell 命令分隔符,因此,我们需要对“;”进行转义从 shell 将其传递给 find 的 -exec 参数。
  • 值得注意的是,-i 本身并不会创建备份文件,而是导致 sed 对文件执行就地操作的原因。
  • {} 是什么?
  • {} 将被find 找到的每个文件名替换,\; 告诉找到他需要执行的命令到此完成。
【解决方案2】:

我更喜欢使用find | xargs cmd 而不是find -exec,因为它更容易记住。

此示例将您当前目录下的 .txt 文件中的“foo”全局替换为“bar”:

find . -type f -name "*.txt" -print0 | xargs -0 sed -i "s/foo/bar/g"

如果您的文件名不包含空格等时髦字符,则可以省略 -print0-0 选项。

【讨论】:

  • 如果你在 OSX 上,试试find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"(注意为-i 参数提供一个空字符串)。
  • 在 MacOS 上,运行 sed -i.bak 而不是 sed -i。我认为正如@JakubKukul 提到的,sed -i '' 也有效。
【解决方案3】:

为了可移植性,我不依赖 sed 特定于 linux 或 BSD 的特性。相反,我使用 Kernighan 和 Pike 关于 Unix 编程环境的书中的 overwrite 脚本。

然后是命令

find /the/folder -type f -exec overwrite '{}' sed 's/old/new/g' {} ';'

overwrite 脚本(我到处使用)是

#!/bin/sh
# overwrite:  copy standard input to output after EOF
# (final version)

# set -x

case $# in
0|1)        echo 'Usage: overwrite file cmd [args]' 1>&2; exit 2
esac

file=$1; shift
new=/tmp/$$.new; old=/tmp/$$.old
trap 'rm -f $new; exit 1' 1 2 15    # clean up files

if "$@" >$new               # collect input
then
    cp $file $old   # save original file
    trap 'trap "" 1 2 15; cp $old $file     # ignore signals
          rm -f $new $old; exit 1' 1 2 15   # during restore
    cp $new $file
else
    echo "overwrite: $1 failed, $file unchanged" 1>&2
    exit 1
fi
rm -f $new $old

这个想法是它仅在命令成功时才覆盖文件。在find 以及您不想使用的地方很有用

sed 's/old/new/g' file > file  # THIS CODE DOES NOT WORK

因为在sed 可以读取文件之前,shell 会截断文件。

【讨论】:

    【解决方案4】:

    我可以建议(在备份您的文件后):

    find /the/folder -type f -exec sed -ibak 's/old/new/g' {} ';'
    

    【讨论】:

      【解决方案5】:

      示例:将 /app/config/ 文件夹及其子文件夹下的所有 ini 文件的 {AutoStart} 替换为 1:

      sed 's/{AutoStart}/1/g' /app/config/**/*.ini
      

      【讨论】:

        【解决方案6】:

        这对我有用(在 mac 终端上,在 Linux 上你不需要 '' -e):

        sed -i '' -e 's/old text/new text/g' `grep 'old text' -rl *`
        

        命令grep 'old text' -rl * 列出了工作目录(和子目录)中存在“旧文本”的所有文件。然后在 sed 中传递。

        【讨论】:

          【解决方案7】:

          可能想试试my mass search/replace Perl script。与链式实用程序解决方案相比具有一些优势(例如不必处理多个级别的 shell 元字符解释)。

          #!/usr/bin/perl
          
          use strict;
          
          use Fcntl qw( :DEFAULT :flock :seek );
          use File::Spec;
          use IO::Handle;
          
          die "Usage: $0 startdir search replace\n"
              unless scalar @ARGV == 3;
          my $startdir = shift @ARGV || '.';
          my $search = shift @ARGV or
              die "Search parameter cannot be empty.\n";
          my $replace = shift @ARGV;
          $search = qr/\Q$search\E/o;
          
          my @stack;
          
          sub process_file($) {
              my $file = shift;
              my $fh = new IO::Handle;
              sysopen $fh, $file, O_RDONLY or
                  die "Cannot read $file: $!\n";
              my $found;
              while(my $line = <$fh>) {
                  if($line =~ /$search/) {
                      $found = 1;
                      last;
                  }
              }
              if($found) {
                  print "  Processing in $file\n";
                  seek $fh, 0, SEEK_SET;
                  my @file = <$fh>;
                  foreach my $line (@file) {
                      $line =~ s/$search/$replace/g;
                  }
                  close $fh;
                  sysopen $fh, $file, O_WRONLY | O_TRUNC or
                      die "Cannot write $file: $!\n";
                  print $fh @file;
              }
              close $fh;
          }
          
          sub process_dir($) {
              my $dir = shift;
              my $dh = new IO::Handle;
              print "Entering $dir\n";
              opendir $dh, $dir or
                  die "Cannot open $dir: $!\n";
              while(defined(my $cont = readdir($dh))) {
                  next
                      if $cont eq '.' || $cont eq '..';
                  # Skip .swap files
                  next
                      if $cont =~ /^\.swap\./o;
                  my $fullpath = File::Spec->catfile($dir, $cont);
                  if($cont =~ /$search/) {
                      my $newcont = $cont;
                      $newcont =~ s/$search/$replace/g;
                      print "  Renaming $cont to $newcont\n";
                      rename $fullpath, File::Spec->catfile($dir, $newcont);
                      $cont = $newcont;
                      $fullpath = File::Spec->catfile($dir, $cont);
                  }
                  if(-l $fullpath) {
                      my $link = readlink($fullpath);
                      if($link =~ /$search/) {
                          my $newlink = $link;
                          $newlink =~ s/$search/$replace/g;
                          print "  Relinking $cont from $link to $newlink\n";
                          unlink $fullpath;
                          my $res = symlink($newlink, $fullpath);
                          warn "Symlink of $newlink to $fullpath failed\n"
                              unless $res;
                      }
                  }
                  next
                      unless -r $fullpath && -w $fullpath;
                  if(-d $fullpath) {
                      push @stack, $fullpath;
                  } elsif(-f $fullpath) {
                      process_file($fullpath);
                  }
              }
              closedir($dh);
          }
          
          if(-f $startdir) {
              process_file($startdir);
          } elsif(-d $startdir) {
              @stack = ($startdir);
              while(scalar(@stack)) {
                  process_dir(shift(@stack));
              }
          } else {
              die "$startdir is not a file or directory\n";
          }
          

          【讨论】:

            【解决方案8】:
            for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 
            

            【讨论】:

            • 请解释你的答案。
            • 虽然此代码可以解决 OP 的问题,但最好包含有关您的代码如何解决 OP 问题的说明。这样,未来的访问者可以从您的帖子中学习,并将其应用到他们自己的代码中。 SO 不是编码服务,而是知识资源。高质量、完整的答案强化了这一想法,并且更有可能获得支持。这些功能,加上所有帖子都是独立的要求,是 SO 作为一个使我们与论坛区分开来的平台的一些优势。您可以编辑以添加其他信息和/或使用源文档补充您的解释。
            • 这不会解决问题,因为它只列出当前工作目录而不列出子文件夹
            【解决方案9】:

            如果文件夹中的文件名有一些常规名称(如 file1、file2...),我已用于循环。

            for i in {1..10000..100}; do sed 'old\new\g' 'file'$i.xml > 'cfile'$i.xml; done
            

            【讨论】:

            • 这与提出的问题无关。该问题未提及有关相同文件/文件夹名称模式的任何内容。请避免这样的答案
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-08-30
            • 2012-05-15
            • 2014-03-23
            • 1970-01-01
            相关资源
            最近更新 更多