sed初心者:フォルダー内のすべての出現箇所の変更


98

フォルダー(およびそのサブフォルダー)内のすべてのファイルを正規表現で検索して置換する必要があります。それを行うためのLinuxシェルコマンドは何でしょうか?

たとえば、これをすべてのファイルに対して実行し、古いファイルを新しい置き換えられたテキストで上書きします。

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

回答:


148

sedだけを使用してそれを行う方法はありません。少なくともfindユーティリティを一緒に使用する必要があります。

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

このコマンドは、.bak変更されたファイルごとにファイルを作成します。

ノート:

  • command の-i引数sedはGNU拡張であるため、BSDでこのコマンドを実行している場合はsed、出力を新しいファイルにリダイレクトして、名前を変更する必要があります。
  • findユーティリティが実装されていません-execので、あなたが使用する必要があります、古いUNIXボックスに引数を| xargs代わりに。

4
何の\;ため?
Andriy Makukha

4
引数-execのコマンドが「;」で終わる場所を見つけるように指示する必要があります。ただし、シェルはシェルコマンドの区切り記号と同じ記号(;)を使用するため、「;」をエスケープする必要があります。シェルからそれをfindの-exec引数に渡します。
osantana

2
注目に値するのは-i、それ自体ではバックアップファイルが作成されず、sedがファイルに対して適切な操作を実行するためです。
カイル

1
何の{}ためですか?
ニックネーム

1
{}各ファイル名に置き換えられますによって発見findし、\;彼はこの時点で仕上げを実行する必要があるコマンドことを発見するように指示します。
オサンタナ

51

覚えやすいので、使うfind | xargs cmdよりも好きfind -execです。

この例では、現在のディレクトリ以下の.txtファイルで「foo」を「bar」にグローバルに置き換えます。

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

-print0そして-0、あなたのファイル名がスペースなどのファンキーな文字が含まれていない場合のオプションは省略することができます。


3
OSXを使用している場合は、試してくださいfind . -type f -name "*.txt" -print0 | xargs -0 sed -i '' "s/foo/bar/g"-i引数に空の文字列を指定することに注意してください)。
Jakub Kukul 2017年

6

移植性については、LinuxまたはBSDに固有のsedの機能には依存しません。代わりにoverwrite、Unixプログラミング環境に関するKernighanとPikeの本のスクリプトを使用します。

コマンドは

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を読み取る前にファイルを切り捨てるためです。


3

私が提案するかもしれません(ファイルをバックアップした後):

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

0

例:/ app / config /フォルダーとその子フォルダーの下にあるすべてのiniファイルに対して、1を{AutoStart}に置き換えます。

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

0
for i in $(ls);do sed -i 's/old_text/new_text/g' $i;done 

5
あなたの答えを説明してください。
ドロップアウト

このコードはOPの問題を解決する可能性がありますが、コードがOPの問題にどのように対処するかについての説明を含めることをお勧めします。このようにして、将来の訪問者はあなたの投稿から学び、それを自分のコードに適用できます。SOはコーディングサービスではなく、知識のリソースです。高品質で完全な回答はこの考えを補強し、賛成される可能性が高くなります。これらの機能に加えて、すべての投稿が自己完結型であるという要件は、フォーラムから私たちを区別するプラットフォームとしてのSOのいくつかの長所です。編集して追加情報を追加したり、ソースドキュメントで説明を補足したりできます。
SherylHohman

1
これが読めない場合は、私の答えを忘れてください。それは単にbashの基本です。
DimiDak

-1

大量検索/ Perlスクリプトの置き換えを試したいかもしれません。チェーンユーティリティソリューションよりもいくつかの利点があります(シェルメタキャラクターの解釈の複数のレベルを処理する必要がないなど)

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

-3

フォルダ内のファイルの名前にいくつかの通常の名前(file1、file2 ...など)がある場合、私はサイクルに使用しました。

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

これは尋ねられた質問とは関係ありません。質問では、同じファイル/フォルダー名のパターンについては何も触れられていません。そのような答えは避けてください
Kunal Parekh
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.