Как работать на sed более чем 10 миллионов файлов в каталоге?

У меня есть каталог, который имеет 10 144 911 файлов в нем. До сих пор я попробовал следующее:

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

Разрушенный моя оболочка, ls находится в tilda, но я не могу выяснить, как сделать тот.

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

Слишком много args для sed

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

Не мог больше разветвлять больше памяти

Какие-либо другие идеи о том, как создать эту добрую команду? Файлы не должны общаться друг с другом. ls | wc -l кажется, работает (очень медленный), таким образом, это должно быть возможно.

16
задан 14.03.2011, 04:30

5 ответов

Дайте этому попытку:

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

Это только подаст одно имя файла к каждому вызову sed. Это решит "слишком много args для sed" проблема. -P опция должна позволить нескольким процессам быть разветвленными одновременно. Если 0 не работает (это, как предполагается, выполняет как можно больше), попробуйте другие числа (10? 100? количество ядер Вы имеете?) для ограничения числа.

19
ответ дан 07.12.2019, 10:50

Я протестировал этот метод (и все другие) на 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' не назвали. Процесс sed почти наверняка будет самым трудоемким, но я думал, что будет интересно видеть как пустые сравненные методы.

Dennis 'find |xargs' метод, с помощью одноядерного, взял *4 часа 21 минута ** дольше, чем bash array метод на a no sed выполненный... Однако многоядерное преимущество, предлагаемое 'находкой', должно перевесить разницу во времени, показанную, когда 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
-----------+---------+---------+----------------------------------------------------- 
7
ответ дан 07.12.2019, 10:50

Другая возможность для абсолютно безопасной находки:

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 )
2
ответ дан 07.12.2019, 10:50

Это главным образом вне темы, но Вы могли использовать

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 ...) скорость: Вы стараетесь не вызывать sed 10 миллионов раз. Это было бы быстрее все еще, если Вы могли бы избегать использования Python (так как Python является довольно медленным, относительно), таким образом, жемчуг мог бы быть лучшим выбором для этой задачи. Я не уверен, как сделать эквивалент удобно с жемчугом.

Путем это работает, это xargs вызовет Python со столькими аргументами, сколько он может соответствовать на единственной командной строке и продолжать делать это, пока он не исчерпывает аргументы (которые предоставляются ls -f *.txt). Количество аргументов каждому вызову будет зависеть от длины имен файлов и, гм, некоторый другой материал. fileinput.input функционируйте приводит к последовательным строкам из файлов, названных в аргументах каждого вызова, и inplace опция говорит этому волшебно "ловить" вывод и использовать его для замены каждой строки.

Обратите внимание что строка Python replace метод не использует regexps; при необходимости в них Вы имеете к import re и используйте print re.sub(line, "blah", "blee"). Они - Совместимые с Perl RegExps, которые являются видом в большой степени укрепленных версий тех, Вы добираетесь с sed -r.

править

Как akira упоминает в комментариях, исходная версия с помощью шарика (ls -f *.txt) вместо find команда не работала бы, потому что шарики обрабатываются оболочкой (bash) самостоятельно. Это означает, что, прежде чем команда даже выполняется, 10 миллионами имен файлов заменят в командную строку. Это, как в значительной степени гарантируют, превысит максимальный размер списка аргументов команды. Можно использовать xargs --show-limits для определенной для системы информации об этом.

Максимальный размер списка аргументов также принят во внимание xargs, который ограничивает количество аргументов, оно передает каждому вызову Python согласно тому пределу. С тех пор xargs должен будет все еще вызвать Python довольно много раз, предложение akira для использования os.path.walk получить список файлов, вероятно, сэкономит Вам некоторое время.

1
ответ дан 07.12.2019, 10:50

Попытка:

ls | while read file; do (something to $file); done
0
ответ дан 07.12.2019, 10:50

Теги

Похожие вопросы