Як запустити sed на понад 10 мільйонів файлів у каталозі?


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, ймовірно, швидше, ніж запуск pythonабо perlтакож, за винятком випадків, якщо ви робите все в цьому інтерпретаторі.
акіра

@akira: Ви хочете сказати, що запустити perl або python раз на стільки файлів, скільки вміститься в командному рядку, дорожче, ніж запускати sed один раз для кожного з цих файлів? Я був би дуже здивований, якби це було так. —————— Напевно, ви не зрозуміли, що моя пропозиція полягає в тому, щоб один раз (або принаймні менше разів - див. Мою відповідь) викликати (запустити) програму редагування, і відкрити її, змінити та зберегти кожен із файлів. своєю чергою, а не за допомогою програми редагування окремо для кожного з цих файлів.
інтуїтив

ваш перший коментар не відображає те, що ви насправді хотіли сказати: "замініть sed на python / perl". швидше, ніж "знайти. -exec sed" .. що, очевидно, не так. у своїй відповіді ви називаєте python набагато частіше, ніж це насправді потрібно.
акіра

Я думаю, що Акіра неправильно трактував вашу (інтуїтивну) пропозицію. Я вважаю, що ви пропонували збирати файли разом. Я спробував це з моєї спроби xargs, час спробувати ще раз :)
Сандро

Відповіді:


19

Спробуйте:

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

Він буде подавати лише одне ім’я файлу для кожного виклику sed. Це вирішить проблему "занадто багато аргументів для sed". -PВаріант повинен дозволити кільком процесам бути роздвоєною одночасно. Якщо 0 не працює (він повинен працювати якомога більше), спробуйте інші числа (10? 100 - кількість ядер у вас?), Щоб обмежити кількість.


3
Ймовірно, потрібно буде find . -name \*.txt -print0уникати того, щоб оболонка розширювала глобус і намагалася виділити простір для 10 мільйонів аргументів, щоб знайти .
Кріс Джонсен

@ChrisJohnsen: Так, це правильно. Я кинувся розміщувати свою відповідь і пропустив, включаючи ті важливі частини. Я відредагував свою відповідь тими виправленнями. Спасибі.
Призупинено до подальшого повідомлення.

Спробуйте зараз ... схрещує пальці
Сандро

7

Я перевірив цей метод (і всі інші) на 10 мільйонах (порожніх) файлів, названих "привіт 00000001" на "привіт 10000000" (14 байт на ім'я).

ОНОВЛЕННЯ: Зараз я включив чотириядерний запуск 'find |xargs'методу (все ще без 'sed'; просто відлуння> / 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" не називався. Процес засідання майже напевно буде найбільш трудомістким, але я подумав, що буде цікаво подивитися, як порівнюють голі методи.

'find |xargs'Метод Денніса , використовуючи одне ядро, зайняв * 4 годин 21 хв ** довше, ніж bash arrayметод на no sedходу ... Однак перевага багатоядерного, запропонована функцією '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
-----------+---------+---------+----------------------------------------------------- 


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 ...) - швидкість: ви уникаєте звернення в sed10 мільйонів разів. Але все-таки було б швидше, якщо ви не зможете використовувати Python (оскільки python начебто повільний, відносно), тому perl може бути кращим вибором для цього завдання. Я не впевнений, як зробити еквівалент зручно за допомогою perl.

Як це працює, це xargsпризведе до того, що Python викликає стільки аргументів, скільки він може вміститися в одному командному рядку, і продовжуватиме це робити, поки у нього не вичерпаються аргументи (які надаються ls -f *.txt). Кількість аргументів до кожного виклику буде залежати від довжини імен файлів і, гм, деяких інших матеріалів. fileinput.inputФункція дає послідовні рядки з файлів , названих в якості аргументів для кожного виклику, в і inplaceопція вказує , що чарівним чином «зловити» вихід і використовувати його для заміни кожного рядка.

Зауважте, що струнний replaceметод Python не використовує регулярні вирази; якщо вони вам потрібні, ви повинні import reі користуватися print re.sub(line, "blah", "blee"). Вони є Perl-сумісними RegExps, які є на зразок сильно укріплених версій тих, з якими ви отримуєте sed -r.

редагувати

Як згадує akira в коментарях, оригінальна версія, що використовує glob ( ls -f *.txt) замість findкоманди, не працюватиме, оскільки глобуси обробляються самою shell ( bash). Це означає, що перед тим, як команда навіть буде запущена, 10 мільйонів імен файлів буде замінено в командний рядок. Це майже гарантовано перевищує максимальний розмір списку аргументів команди. Ви можете використовувати xargs --show-limitsдля цього інформацію про систему.

Максимальний розмір списку аргументів також враховується тим xargs, що обмежує кількість аргументів, які він передає, до кожного виклику python відповідно до цієї межі. Оскільки xargsдоведеться ще кілька разів викликати python, пропозиція Akira використовувати os.path.walkдля отримання списку файлів, ймовірно, заощадить ваш час.


1
який сенс використовувати глобальний оператор (який все одно не вдасться до такої кількості файлів) ... а потім подавати файли на python, який має os.path.walk()?
акіра

@akira: глобальний оператор повинен уникати спроб замінити вміст .і ... Звичайно, є й інші способи (тобто find), але я намагаюся якомога ближче дотримуватися того, що розуміє ОП. Це також є причиною невикористання os.path.walk.
інтуїтив

@akira: Хоча гарна пропозиція, можливо, це буде значно швидше.
інтуїтив

Я думаю, що ОП зрозуміють os.path.walkдосить легко.
акіра

0

Спробуйте:

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

2
ls -fбуло б краще; Ви дійсно хочете почекати його stat()і сортувати стільки файлів?
geekosaur

зараз я намагаюся: для f в * .txt; робити бла; зроблено. Я дам цього удару, якщо не вдасться. Дякую!
Сандро
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.