サイズを小さくする必要がある膨大な数のファイルがあります。ほとんどの(すべてではない)ファイルには、情報を失うことなく切り取ることができる終了セクションがあることがわかりました。
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
あなたが探しているコマンドのシーケンスは
/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
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
の呼び出し回数も最小限に抑えられます。
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}'
単純にして、ファイル操作部分に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'
このpython
3.8ソリューションは、Stephaneのインプレースtruncate
solution に大まかに基づいていますが、いくつかの違いがあります。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())
Sundeep’s answer と Ed 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 -q
qファイルに(END DATA
)が検索されるパターンが含まれている場合、「「成功」の終了ステータスですぐに終了し、それ以外の場合は「false」。-a
は、「AND」を意味します。シェルコマンドラインの&&
と同じです。 「前の処理が成功した場合にのみ、次の処理を実行する」という意味です。実際、これはfind
述語(テスト/アクション)間のデフォルトの結合演算子なので、省略できます。わかりやすくするためだけに含めました。sed
コマンド(ただし、foo
がNEW 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'
nextfile
(gawk
、bwk
、mawk
[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ではサポートされていません。