web-dev-qa-db-ja.com

特定のパターンが発生する行からファイルをカットする方法は?

サイズを小さくする必要がある膨大な数のファイルがあります。ほとんどの(すべてではない)ファイルには、情報を失うことなく切り取ることができる終了セクションがあることがわかりました。

Data 1
Data 2
something_unimportant_here END DATA
Rubbish 1
Rubbish 2

「END DATA」以降のすべての行を削除し、パターンを含むファイルのみを変更して、ファイル(したがって、すべて)を編集して、ディスクへの書き込みアクセスを最小限に抑える方法(多くの場合、多くの場合)ファイルと遅いディスク)。

可能であれば、新しい最後の行をファイル(自分の終了タグ)に追加して、ファイルの構文が正しいままになるようにします(パターンを含むファイルでのみ)。

edの使用を考えていました。

echo ',s/END DATA/ ???? '\\n'q'\\n'wq' | ed "$file"

しかし、管理できないようです????正しい部分。

期待される出力:

Data 1
Data 2
NEW END
7
Ned64

あなたが探しているコマンドのシーケンスは

/END DATA/,$d
q
.a
NEW END
.
wq

またはワンライナーとして

printf '%s\n' '/END DATA/,$d' 'q' '.a' 'NEW END' '.' 'wq'

(テストのためにwq,pに置き換えることができます。)

例与えられた

$ cat file
Data 1
Data 2
something_unimportant_here END DATA
Rubbish 1
Rubbish 2

その後

$ printf '%s\n' '/END DATA/,$d' 'q' '.a' 'NEW END' '.' 'wq' | ed -s file

与える

$ cat file
Data 1
Data 2
NEW END
6
steeldriver

sed -i/Perl -i/ed/gawk -i inplaceのようにファイルの新しいコピーを書き込む必要なしに、ファイルを所定の場所で切り捨てることでそれを行うことができるはずです行う。 Perlの場合:

find . -name '*.txt' -type f -exec Perl -ne '
  BEGIN{@ARGV=map{"+<$_"}@ARGV} # open files in read+write mode in the
                                # while(<>) loop implied by -n
  if (/END DATA/) {
    seek ARGV,-length,1; # back to beginning of matching line
    print ARGV "NEW END\n";
    truncate ARGV, tell ARGV;
    close ARGV; # skip to next file
  }' {} +

Perlが一致を見つけるとすぐに読み取りを停止し、NEW END\nのみが書き込みを行うため、I/Oが最小限に抑えられます。また、書き込みも適切に行われるため、ファイルのメタデータ(所有権、権限、ACL、スパース性...)は保持され、ハードリンクは破損しません。

-exec {} +を使用すると、Perlの呼び出し回数も最小限に抑えられます。

8

GNU grepおよびGNU sed

grep -lZ 'END DATA' *.txt | xargs -0 sed -i -e '/END DATA/,${//i foo' -e 'd}'

ここで、*.txtは、すべてのファイルが.txt拡張子で終わる現在のディレクトリにあると想定しています。ファイルを再帰的に検索する必要がある場合、GNU grep-r/-Rオプションもサポートします。

/END DATA/,$操作する行の範囲

//i fooここ//は、以前に使用された正規表現と一致します。つまり、/END DATA/およびiコマンドは、必要に応じて新しい終了マーカーを追加します

iコマンドは改行で区切る必要があるため、dコマンドを区切るために-eオプションを使用して、範囲に一致するすべての行を削除します

別の方法として、これを使用することもできますが、一度に1つのファイルのみがsedに渡されます。

grep -lZ 'END DATA' *.txt | xargs -0 -n1 sed -i -e '/END DATA/{i foo' -e 'Q}'
6
Sundeep

単純にして、ファイル操作部分にawkを使用します。たとえば、GNU find、awk、grep、xargsを使用します。

find . -type f -exec grep -lZ 'END DATA' {} + |
    xargs -r0 awk -i inplace '/END DATA/{nextfile} 1'

または、すべてのファイルの最後に独自の終了タグを印刷します。

... |
  xargs -r0 awk -i inplace '/END DATA/{print "NEW END"; nextfile} 1'
3
Ed Morton

このpython 3.8ソリューションは、Stephaneのインプレースtruncatesolution に大まかに基づいていますが、いくつかの違いがあります。1.コードは、ディレクトリトラバーサルの外部ユーティリティに依存していません。 2. END DATA文字列を簡単に見つけられるように、ファイルはメモリマップされています

コードを.pyファイルに配置し、ディレクトリ名をパラメーターとして渡します

import mmap
import os
import sys
from contextlib import closing

def yield_all_files(dir_):
    for root, dir_, files in os.walk(dir_):
        yield from (os.path.join(root, file_) for file_ in files if file_.endswith('.txt'))

if __name__ == '__main__':
    old = b'END DATA'
    new = b'NEW END\n'
    dir_ = sys.argv[1]
    for file_ in yield_all_files(dir_):
        with open(file_, mode='r+b') as f:
            with closing(mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_WRITE)) as mm:
                if (loc := mm.find(old)) > -1:
                    mm.seek(loc)
                    mm.write(new)
                    mm.resize(mm.tell()) 
2
iruvar

Sundeep’s answerEd Morton’s answer を組み合わせますが、xargsはありません:

見つける  -type f -name '* .txt' …(その他の基準)… -exec grep -q 'END DATA' {} ';' -a \
 -exec sed -i -e '/ END DATA /、$ {// i NEW END' -e 'd}' {} +
  • もちろん、findはファイルを選択します。デフォルトでは、指定されたディレクターを再帰的に検索します。現在のディレクトリのみを検索するには、-maxdepth 1の後に.を追加します。
  • grep -qqファイルに(END DATA)が検索されるパターンが含まれている場合、「「成功」の終了ステータスですぐに終了し、それ以外の場合は「false」。
  • -aは、「AND」を意味します。シェルコマンドラインの&&と同じです。 「前の処理が成功した場合にのみ、次の処理を実行する」という意味です。実際、これはfind述語(テスト/アクション)間のデフォルトの結合演算子なので、省略できます。わかりやすくするためだけに含めました。
  • したがって、Sundeepの回答から逐語的にコピーされたsedコマンド(ただし、fooNEW ENDに変更されたもの)は、END DATA文字列を含むファイルでのみ実行されます。他のfindテストを満たします。
  • -exec … +を指定すると、sedと同様に、xargsが複数のファイルで1回呼び出されます。
    grepコマンドで-exec … +を使用することはできません。これは、終了ステータスをテストできないためです。

Awkを使用してパターンのオフセットを見つけ、ddを使用してその時点でファイルを切り捨て、新しいトレーラーを追加します。

# usage truncatoo pattern new_end find_args ...
truncatoo(){
    pat=$1; shift; tail=$1; shift
    LC_CTYPE=C TAIL=$tail find "$@" -exec awk -v q="'" "$pat"'{
           gsub(q,q"\\"q q,FILENAME);
           system("printf \"$TAIL\" | dd bs="l" seek=1 of="q FILENAME q" 2>/dev/null");
           exit
       }
       {l+=length()+1}
    ' {} \;
}

truncatoo '/END DATA/' 'NEW END\n' file.txt
truncatoo '/END DATA/' 'NEW END\n' . -type f -name '*.txt'

nextfilegawkbwkmawk [1]の一部のバージョン)をサポートするawk実装では、バッチを渡すことでより効率的に実行できますawkするファイルの数:

# usage truncatoo pattern new_end find_args ...
truncatoo(){
    pat=$1; shift; tail=$1; shift
    LC_CTYPE=C TAIL=$tail find "$@" -exec awk -v q="'" "$pat"'{
           gsub(q,q"\\"q q,FILENAME);
           system("printf \"$TAIL\" | dd bs="l" seek=1 of="q FILENAME q" 2>/dev/null");
           l=0; nextfile
       }
       {l+=length()+1}
    ' {} +
}
$ file="a'b\$q  * r"; seq 1 100 >"$file"
$ truncatoo /7/ 'CUT\n' "$file"; cat "$file"
1
2
3
4
5
6
CUT

かっこいい2>/dev/nullの代わりにstatus=noxferをサポートするdd実装で使用できます。

渡すkludgeと環境変数の引用はめちゃくちゃで、多少の改善が必要かもしれません。

[1]:GNU awk manual によると、mawkでもサポートされているはずですが、Debian 10の古いバージョンのmawkではサポートされていません。

0
mosvy