ディレクトリ内の1,000万を超えるファイルでsedを実行する方法は?


16

10144911ファイルがあるディレクトリがあります。これまでのところ、次のことを試しました。

  • for f in ls; do sed -i -e 's/blah/blee/g' $f; done

シェルがクラッシュしlsました。チルダになっていますが、どのように作成するかわかりません。

  • ls | xargs -0 sed -i -e 's/blah/blee/g'

引数が多すぎます sed

  • find . -name "*.txt" -exec sed -i -e 's/blah/blee/g' {} \;

これ以上メモリをフォークできませんでした

この親切なコマンドを作成する方法に関する他のアイデアはありますか?ファイルは互いに通信する必要はありません。ls | wc -l動作しているように見える(非常に遅い)ので、可能に違いない。


1
sed各ファイルの呼び出しを回避できれば、より高速になります。に一連のファイルを開いたり、編集したり、保存したり、閉じたりする方法があるかどうかはわかりませんsed。速度が重要な場合は、perlまたはpythonなどの別のプログラムを使用することをお勧めします。
直観

@intuited:ファイルにまったく何もしないと、もっと速くなるでしょう...真剣に?一連のファイルのパターンを変更する場合は、パターンがあるかどうかを確認するために各ファイルを調べる必要があります。「いくつかの」ファイルをスキップできることを事前に知っている場合は、ファイルに触れないようにする方が明らかに高速です。そして、の起動時間はsed、おそらく速く起動するよりもですpythonperlあなたは場合を除き、同様にすべてをその通訳に。
アキラ

@akira:perlまたはpythonをコマンドラインに収まる限り多くのファイルに対して1回起動するのは、それらのファイルごとにsedを1回起動するよりもコストがかかると言っていますか?もしそうだとしたら、本当に驚きます。——————私の提案は、編集プログラムを1回(または少なくとも数回-私の回答を参照)呼び出し(開始)、各ファイルを開いて変更し、再保存することです。これらのファイルごとに個別に編集プログラムを呼び出すのではなく、順番に。
直観

あなたの最初のコメントは、あなたが本当に言いたかったことを反映していません: "python / perlによるsedの置き換え" ..それを実行し、コマンドラインOPが与えた@を見るだけで、無邪気な読者は "find。-exec python"が「find。-exec sed」..よりも高速ですが、明らかにそうではありません。あなた自身の答えでは、実際に必要とされるよりもはるかに頻繁にpythonを呼び出します。
アキラ

アキラはあなたの(直感的な)提案を誤って解釈したと思います。私はあなたが一緒にファイルを束ねることを提案していたと信じています。私はxargsの試みでそれを試しました。もう一度試してみましょう:)
サンドロ

回答:


19

これを試してみてください:

find -name '*.txt' -print0 | xargs -0 -I {} -P 0 sed -i -e 's/blah/blee/g' {}

の各呼び出しに1つのファイル名のみをフィードしますsed。これにより、「sedの引数が多すぎる」問題が解決されます。この-Pオプションでは、複数のプロセスを同時にフォークできるようにする必要があります。0が機能しない場合(可能な限り多く実行されるはずです)、他の数値(10?100?所有するコアの数?)を試して、数を制限してください。


3
おそらく、find . -name \*.txt -print0シェルがglobを展開するのを避け、findに 1,000万個の引数のスペースを割り当てようとする必要があります
クリスジョンセン

@ChrisJohnsen:はい、それは正しいです。私は答えを急いで投稿し、それらの重要な部分を含めて逃しました。これらの修正を加えて回答を編集しました。ありがとう。
追って通知があるまで一時停止します。

今すぐ試してみてください... 指を交差させる
サンドロ

7

「hello 00000001」から「hello 10000000」(名前ごとに14バイト)という名前の1,000 (空の)ファイルでこのメソッド(およびその他すべて)をテストしました。

更新:メソッドにクアッドコアの実行を 含めました'find |xargs'(まだ「sed」はありません。単にecho> / dev / null)。

# Step 1. Build an array for 10 million files
#   * RAM usage approx:  1.5 GiB 
#   * Elapsed Time:  2 min 29 sec 
  names=( hello\ * )

# Step 2. Process the array.
#   * Elapsed Time:  7 min 43 sec
  for (( ix=0, cnt=${#names[@]} ; ix<$cnt; ix++ )) ; do echo "${names[ix]}" >/dev/null ; done  

上記のテストデータに対して実行された場合、提供された回答がどのように運ばれたかの概要を以下に示します。これらの結果には、基本的なオーバーヘッドのみが含まれます。すなわち、「sed」は呼び出されませんでした。sedプロセスはほぼ確実に最も時間がかかりますが、私は、むき出しのメソッドがどのように比較されるかを見るのは面白いと思いました。

'find |xargs'シングルコアを使用するデニスの方法はbash arrayno sed実行時の方法よりも* 4時間21分**時間がかかりました...ただし、「find」によって提供されるマルチコアの利点は、sedが呼び出されたときに示される時間差を上回るはずです。ファイルを処理しています...

           | Time    | RAM GiB | Per loop action(s). / The command line. / Notes
-----------+---------+---------+----------------------------------------------------- 
Dennis     | 271 min | 1.7 GiB | * echo FILENAME >/dev/null
Williamson   cores: 1x2.66 MHz | $ time find -name 'hello *' -print0 | xargs -0 -I {} echo >/dev/null {}
                               | Note: I'm very surprised at how long this took to run the 10 million file gauntlet
                               |       It started processing almost immediately (because of xargs I suppose),  
                               |       but it runs **significantly slower** than the only other working answer  
                               |       (again, probably because of xargs) , but if the multi-core feature works  
                               |       and I would think that it does, then it could make up the defecit in a 'sed' run.   
           |  76 min | 1.7 GiB | * echo FILENAME >/dev/null
             cores: 4x2.66 MHz | $ time find -name 'hello *' -print0 | xargs -0 -I {} -P 0 echo >/dev/null {}
                               |  
-----------+---------+---------+----------------------------------------------------- 
fred.bear  | 10m 12s | 1.5 GiB | * echo FILENAME >/dev/null
                               | $ time names=( hello\ * ) ; time for (( ix=0, cnt=${#names[@]} ; ix<$cnt; ix++ )) ; do echo "${names[ix]}" >/dev/null ; done
-----------+---------+---------+----------------------------------------------------- 
l0b0       | ?@#!!#  | 1.7 GiB | * echo FILENAME >/dev/null 
                               | $ time  while IFS= read -rd $'\0' path ; do echo "$path" >/dev/null ; done < <( find "$HOME/junkd" -type f -print0 )
                               | Note: It started processing filenames after 7 minutes.. at this point it  
                               |       started lots of disk thrashing.  'find' was using a lot of memory, 
                               |       but in its basic form, there was no obvious advantage... 
                               |       I pulled the plug after 20 minutes.. (my poor disk drive :(
-----------+---------+---------+----------------------------------------------------- 
intuited   | ?@#!!#  |         | * print line (to see when it actually starts processing, but it never got there!)
                               | $ ls -f hello * | xargs python -c '
                               |   import fileinput
                               |   for line in fileinput.input(inplace=True):
                               |       print line ' 
                               | Note: It failed at 11 min and approx 0.9 Gib
                               |       ERROR message: bash: /bin/ls: Argument list too long  
-----------+---------+---------+----------------------------------------------------- 
Reuben L.  | ?@#!!#  |         | * One var assignment per file
                               | $ ls | while read file; do x="$file" ; done 
                               | Note: It bombed out after 6min 44sec and approx 0.8 GiB
                               |       ERROR message: ls: memory exhausted
-----------+---------+---------+----------------------------------------------------- 

2

完全に安全な検索の別の機会:

while IFS= read -rd $'\0' path
do
    file_path="$(readlink -fn -- "$path"; echo x)"
    file_path="${file_path%x}"
    sed -i -e 's/blah/blee/g' -- "$file_path"
done < <( find "$absolute_dir_path" -type f -print0 )

1

これはほとんどトピック外ですが、使用できます

find -maxdepth 1 -type f -name '*.txt' | xargs python -c '
import fileinput
for line in fileinput.input(inplace=True):
    print line.replace("blah", "blee"),
'

ここでの主な利点は(以上... xargs ... -I {} ... sed ...)速度です:sed1000万回の呼び出しを回避できます。Pythonの使用を避けることができれば(Pythonは比較的遅いので)それでも高速になるので、このタスクにはperlの方が良い選択かもしれません。perlで同等のことを便利に行う方法がわかりません。

これが機能する方法はxargs、1つのコマンドラインに収まる限り多くの引数でPythonを呼び出し、引数(によって提供されるls -f *.txt)がなくなるまでそれを続けます。各呼び出しの引数の数は、ファイル名の長さ、およびその他の要素によって異なります。このfileinput.input関数は、各呼び出しの引数で指定されたファイルから連続した行を生成し、inplaceオプションは、出力を魔法のように「キャッチ」し、それを使用して各行を置き換えるように指示します。

Pythonの文字列replaceメソッドは正規表現を使用しないことに注意してください。それらが必要な場合は、import reを使用する必要がありますprint re.sub(line, "blah", "blee")。これらは、Perl互換のRegExpです。これは、入手したもののかなり強化されたバージョンですsed -r

編集する

アキラがコメントで言及しているように、グロブ(ls -f *.txtfindakiraがに、コマンド)はシェル(bash)自体によって処理されるため、コマンドの代わりに)は機能しません。これは、コマンドが実行される前に、1,000万のファイル名がコマンドラインに置き換えられることを意味します。これは、コマンドの引数リストの最大サイズを超えることがほとんど保証されています。xargs --show-limitsこれに関するシステム固有の情報に使用できます。

引数リストの最大サイズも考慮されますxargs。これにより、その制限に従ってpythonの各呼び出しに渡す引数の数が制限されます。以来はxargsまだ、かなりの数回のPython呼び出すために使用するアキラの提案を持っていますos.path.walkあなたのセーブおそらくファイルリストを取得するためにいくつかの時間をします。


1
グロブ演算子を使用するポイントはos.path.walk()何ですか(とにかくその多くのファイルに対して失敗します)...そして、持っているPythonにファイルをフィードしますか?
アキラ

@akira:glob演算子は、.との内容を置き換えようとするのを避けること..です。確かに他の方法もあります(つまりfind)が、私はOPが理解するものにできるだけ近づけるようにしています。これもを使用しない理由ですos.path.walk
直観

@akira:しかし、良い提案は、おそらくかなり速くなるでしょう。
直観

OPはos.path.walk非常に簡単に理解できると思います。
アキラ

0

試してください:

ls | while read file; do (something to $file); done

2
ls -f方が良いだろう; あなたは本当にそれを待って、stat()その多くのファイルをソートしたいですか?
ギコサウルス

今私はしようとしています:for。何とかする; できた。それが失敗した場合、私はそれを強打します。ありがとうございました!
サンドロ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.