кошка очень большое количество файлов в правильном порядке

23

У меня есть около 15 000 файлов, которые называются file_1.pdb, file_2.pdbи т.д. Я могу кот около нескольких тысяч из них в порядке, выполнив:

cat file_{1..2000}.pdb >> file_all.pdb

Однако, если я сделаю это для 15 000 файлов, я получу ошибку

-bash: /bin/cat: Argument list too long

Я видел, как эта проблема решалась, find . -name xx -exec xxно это не сохраняло бы порядок соединения файлов. Как мне этого добиться?

нитрат натрия
источник
3
Как называется десятый файл? (Или любой файл с порядком нумерации, состоящим более чем из одной цифры.)
roaima
У меня (сейчас) 15 000 таких файлов в каталоге, и ваша cat file_{1..15000}.pdbконструкция отлично работает для меня.
Ройма
11
в зависимости от системы, какой предел. getconf ARG_MAXдолжен сказать.
ilkkachu
3
Попробуйте изменить свой вопрос на «тысячи» или «очень большое количество» файлов. Может облегчить поиск вопроса для других людей с похожей проблемой.
msouth

Ответы:

49

Используя find, sortи xargs:

find . -maxdepth 1 -type f -name 'file_*.pdb' -print0 |
sort -zV |
xargs -0 cat >all.pdb

Команда findнаходит все соответствующие файлы, а затем печатает их имена путей, sortчто делает «сортировку версий», чтобы получить их в правильном порядке (если бы числа в именах файлов были заполнены нулями до фиксированной ширины, в которой мы бы не нуждались -V). xargsберет этот список отсортированных путей и запускает catих как можно большими партиями.

Это должно работать, даже если имена файлов содержат странные символы, такие как переводы строк и пробелы. Мы используем -print0с, findчтобы дать sortnul-завершенные имена для сортировки, и sortобрабатывает их, используя -z. xargsтоже читает имена с нулем в конце со своим -0флагом.

Обратите внимание, что я записываю результат в файл, имя которого не соответствует шаблону file_*.pdb.


Вышеупомянутое решение использует некоторые нестандартные флаги для некоторых утилит. Они поддерживаются GNU-реализацией этих утилит и, по крайней мере, OpenBSD и реализацией macOS.

Используются нестандартные флаги

  • -maxdepth 1, чтобы сделать findтолько войти в самый верхний каталог, но без подкаталогов. POSIXly, используйтеfind . ! -name . -prune ...
  • -print0, чтобы сделать findвыходные имена с нулевым символом в конце (это было рассмотрено POSIX, но отклонено). Можно использовать -exec printf '%s\0' {} +вместо этого.
  • -z, чтобы сделать sortнуль-завершенные записи. POSIX-эквивалентности нет.
  • -V, чтобы сделать sortсортировку, например, 200после 3. Эквивалентности POSIX не существует, но ее можно заменить числовой сортировкой в ​​определенных частях имени файла, если имена файлов имеют фиксированный префикс.
  • -0, чтобы xargsпрочитать нуль-завершенные записи. POSIX-эквивалентности нет. POSIXly, нужно было бы заключить в кавычки имена файлов в формате, распознаваемом xargs.

Если имена путей ведут себя хорошо, и если структура каталогов плоская (без подкаталогов), то можно обойтись без этих флагов, кроме как -Vс помощью sort.

Кусалананда
источник
1
Вам не нужно нестандартное нулевое завершение для этого. Эти имена файлов являются чрезвычайно скучными, и инструменты POSIX полностью способны обрабатывать их.
Кевин
6
Вы могли бы также написать это более кратко со спецификацией аскера printf ‘file_%d.pdb\0’ {1..15000} | xargs -0 catили даже с точкой зрения Кевина echo file_{1..15000}.pdb | xargs cat. findРешение имеет значительно более накладные расходы , так как он должен искать файловую систему для этих файлов, но это более полезно , когда некоторые файлы могут не существовать.
Кодзиро
4
@Kevin, хотя то, что вы говорите, правда, возможно, лучше иметь ответ, который применим в более общих обстоятельствах. Из следующей тысячи людей, у которых есть этот вопрос, вполне вероятно, что у некоторых из них в именах файлов будут пробелы или что-то еще.
18:00
1
@chrylis Перенаправление никогда не является частью аргументов команды, а xargsскорее catперенаправляется (каждый catвызов будет использовать xargsстандартный вывод). Если бы мы сказали, xargs -0 sh -c 'cat >all.pdb'то было бы разумно использовать >>вместо >, если вы на это намекаете.
Кусалананда
1
Похоже, sort -n -k1.6будет работать (для оригинала, file_nnnимен файлов или sort -n -k1.5для тех, кто не подчеркивание).
Скотт
14

С zsh(откуда {1..15000}приходит этот оператор):

autoload zargs # best in ~/.zshrc
zargs file_{1..15000}.pdb -- cat > file_all.pdb

Или для всех file_<digits>.pdbфайлов в числовом порядке:

zargs file_<->.pdb(n) -- cat > file_all.pdb

(где <x-y>не является Глоб оператор , который соответствует по десятичных чисел х у. При отсутствии xни y, это любое десятичное число. Эквивалент extendedglob«s [0-9]##или kshglob» s +([0-9])(одна или несколько цифр)).

С ksh93помощью встроенной catкоманды (на нее не влияет этот предел execve()системного вызова, поскольку нет выполнения ):

command /opt/ast/bin/cat file_{1..15000}.pdb > file_all.pdb

С bash/ zsh/ ksh93(что поддержка zsh«s {x..y}и имеют printfвстроенный):

printf '%s\n' file_{1..15000}.pdb | xargs cat > file_all.pdb

В системе GNU или совместимой вы также можете использовать seq:

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

Для xargsрешений на основе, особое внимание должно быть уделено именам файлов, которые содержат пробелы, одинарные или двойные кавычки или обратную косую черту.

Как для -It's a trickier filename - 12.pdb, используйте:

seq -f "\"./-It's a trickier filename - %.17g.pdb\"" 15000 |
  xargs cat > file_all.pdb
Стефан Шазелас
источник
Это seq -f | xarg cat > самое элегантное и эффективное решение. (ПО МОЕМУ МНЕНИЮ).
Хастур
Проверьте более хитрое имя файла ... может быть '"./-It'\''s a trickier filename - %.17g.pdb"'?
Хастур
@ Хастур, ой! Да, спасибо, я изменил его на альтернативный синтаксис цитирования. Ваш будет работать так же.
Стефан Шазелас
11

Цикл for возможен и очень прост.

for i in file_{1..15000}.pdb; do cat $i >> file_all.pdb; done

Недостатком является то, что вы вызываете catад много раз. Но если вы не можете точно вспомнить, как с этим работать, findи затраты на вызовы не так уж и плохи в вашей ситуации, то стоит помнить.

OmnipotentEntity
источник
Я часто добавляю echo $i;в тело цикла в качестве «индикатора прогресса»
Рольф
3
seq 1 15000 | awk '{print "file_"$0".dat"}' | xargs cat > file_all.pdb
LarryC
источник
1
AWK может сделать работу НомерСтарта здесь и далее может сделать работу AWK в: seq -f file_%.10g.pdb 15000. Обратите внимание, что seqэто не стандартная команда.
Стефан Шазелас
Спасибо Стефану - я думаю seq -f , это отличный способ сделать это; запомню это.
LarryC
2

посылка

Вы не должны подвергаться этой ошибке только для файлов 15k с определенным форматом имени [ 1 , 2 ] .

Если вы запускаете это расширение из другого каталога и вам нужно добавить путь к каждому файлу, размер вашей команды будет больше, и, конечно, это может произойти.

Решение запустить команду из этого каталога.

(cd That/Directory ; cat file_{1..2000}.pdb >> file_all.pdb )

Лучшее решение Если вместо этого я угадал, и вы запускаете его из каталога, в котором находятся файлы ...
ИМХО, лучшее решение - это Стефан Шазелас :

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

с printf или seq; протестировано на 15k файлах, в которых предварительно кэшировано только их число, оно даже быстрее (в настоящее время, за исключением OP из того же каталога, в котором находятся файлы).

Еще несколько слов

Вы должны быть в состоянии передавать командную строку вашей оболочки более долго.
Длина вашей командной строки составляет 213914 символов и содержит 15003 слова
cat file_{1..15000}.pdb " > file_all.pdb" | wc

... даже добавление 8 байт для каждого слова составляет 333 938 байт (0,3 М), намного ниже, чем 2097142 (2,1 МБ), о которых сообщалось ARG_MAXв ядре 3.13.0 или немного меньшем 2088232, о котором сообщалось как "Максимальная длина команды, которую мы могли бы на самом деле использовать " поxargs --show-limits

Посмотрите на вашу систему на вывод

getconf ARG_MAX
xargs --show-limits

Решение для лени

В таких случаях я предпочитаю работать с блоками даже потому, что, как правило, получается эффективное по времени решение.
Логика (если есть) в том, что я слишком ленив, чтобы писать 1 ... 1000 1001..2000 и т. Д.
И т. Д. Поэтому я прошу сценарий сделать это для меня.
Только после того, как я проверил правильность вывода, я перенаправил его в скрипт.

... но лень это состояние души .
Поскольку у меня аллергия на xargs(я действительно должен был использовать xargsздесь), и я не хочу проверять, как его использовать, я заканчиваю пунктуально, чтобы заново изобрести колесо, как в примерах ниже (tl; dr).

Обратите внимание, что, поскольку имена файлов контролируются (без пробелов, новых строк ...), вы можете легко выполнить что-то вроде приведенного ниже сценария.

ТЛ; др

Версия 1: передать в качестве необязательного параметра 1-й номер файла, последний, размер блока, выходной файл

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;  
    cat $(seq -f file_%.17g.pdb $CurrentStart $CurrentEnd)  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    cat $(seq -f file_%.17g.pdb $CurrentStart $EndN)  >> $OutFile;

Версия 2

Вызов bash для расширения (немного медленнее в моих тестах ~ 20%).

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;
    echo  cat file_{$CurrentStart..$CurrentEnd}.pdb | /bin/bash  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    echo  cat file_{$CurrentStart..$EndN}.pdb | /bin/bash  >> $OutFile;

Конечно, вы можете пойти дальше и полностью избавиться от seq [ 3 ] (от coreutils) и работать напрямую с переменными в bash, или использовать python, или скомпилировать программу ac для этого [ 4 ] ...

Hastur
источник
Обратите внимание, что %gэто сокращение от %.6g. Например, 1 000 + 6 будет представлять 1 000 000.
Стефан Шазелас
Действительно ленивые люди используют инструменты , предназначенные для выполнения этой задачи работы вокруг этого E2BIG ограничения , как xargs, ЗШ - й zargsили ksh93«s command -x.
Стефан Шазелас
seqэто не встроенная команда bash, это команда из GNU coreutils. seq -f %g 1000000 1000000выводит 1e + 06 даже в последней версии coreutils.
Стефан Шазелас
@ StéphaneChazelas Лень - это состояние души. Странно сказать, но мне удобнее, когда я вижу (и визуально проверяю вывод сериализованной команды) и только потом перенаправляю на выполнение. Эта конструкция заставляет меня думать меньше, чем xarg... но я понимаю, что это личное и, возможно, связано только со мной.
Hastur
@ StéphaneChazelas Gotcha, верно ... Исправлено. Спасибо. Я тестировал только с 15k файлов, предоставленных ОП, мой плохо.
Hastur
0

Еще один способ сделать это может быть

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
glglgl
источник