Bash "эквивалент карты": выполнить команду для каждого файла
У меня часто есть команда, которая обрабатывает один файл, и я хочу запустить его для каждого файла в каталоге. Есть ли встроенный способ сделать это?
Например, скажем, у меня есть программа data
, которая выводит важный номер файла:
./data foo
137
./data bar
42
Я хочу запустить его в каждом файле в каталоге примерно так:
map data `ls *`
ls * | map data
чтобы получить результат следующим образом:
foo: 137
bar: 42
Ответы
Ответ 1
Если вы просто пытаетесь выполнить свою программу data
на кучу файлов, самым простым/наименее сложным способом является использование -exec
в find
.
Предположим, что вы хотите выполнить data
во всех txt файлах в текущем каталоге (и подкаталогах). Это все, что вам нужно:
find . -name "*.txt" -exec data {} \;
Если вы хотите ограничить его текущим каталогом, вы можете сделать это:
find . -maxdepth 1 -name "*.txt" -exec data {} \;
Существует множество опций с find
.
Ответ 2
Если вы просто хотите запустить команду для каждого файла, вы можете сделать это:
for i in *; do data "$i"; done
Если вы также хотите отобразить имя файла, в котором он сейчас работает, вы можете использовать это:
for i in *; do echo -n "$i: "; data "$i"; done
Ответ 3
Похоже, вы хотите xargs
:
find . --maxdepth 1 | xargs -d'\n' data
Чтобы напечатать каждую команду сначала, она становится немного сложнее:
find . --maxdepth 1 | xargs -d'\n' -I {} bash -c "echo {}; data {}"
Ответ 4
Вам следует избегать синтаксический анализ ls
:
find . -maxdepth 1 | while read -r file; do do_something_with "$file"; done
или
while read -r file; do do_something_with "$file"; done < <(find . -maxdepth 1)
Последний не создает подоболочку из цикла while.
Ответ 5
Общими методами являются:
ls * | while read file; do data "$file"; done
for file in *; do data "$file"; done
Второй может столкнуться с проблемами, если у вас есть пробелы в именах файлов; в этом случае вы, вероятно, захотите убедиться, что он работает в подоболочке, и установите IFS:
( IFS=$'\n'; for file in *; do data "$file"; done )
Вы можете легко обернуть первую в script:
#!/bin/bash
# map.bash
while read file; do
"$1" "$file"
done
который может быть выполнен по вашему желанию - просто будьте осторожны, чтобы случайно не выполнить ничего немого. Преимущество использования конструкции looping заключается в том, что вы можете легко разместить несколько команд внутри него как часть однострочного интерфейса, в отличие от xargs, где вам нужно будет поместить их в исполняемый файл script для его запуска.
Конечно, вы также можете просто использовать утилиту xargs
:
find -maxdepth 0 * | xargs -n 1 data
Обратите внимание, что вы должны убедиться, что индикаторы отключены (ls --indicator-style=none
), если вы их обычно используете, или @
, добавленные к символическим ссылкам, превратят их в несуществующие имена файлов.
Ответ 6
GNU Parallel специализируется на создании таких отображений:
parallel data ::: *
Он будет запускать одно задание на каждом ядре ЦП параллельно.
GNU Parallel - это общий параллелизатор, который упрощает запуск заданий параллельно на одном компьютере или на нескольких компьютерах, к которым у вас есть доступ к ssh.
Если у вас есть 32 разных задания, которые вы хотите запустить на 4-х процессорах, прямой способ распараллеливания - запустить 8 заданий для каждого процессора:
![Simple scheduling]()
GNU Parallel вместо этого запускает новый процесс, когда заканчивается - сохранение активных процессоров и, следовательно, экономия времени:
![GNU Parallel scheduling]()
Установка
Если GNU Parallel не упакован для вашего дистрибутива, вы можете выполнить личную установку, которая не требует доступа root. Это можно сделать за 10 секунд, выполнив следующие действия:
(wget -O - pi.dk/3 || curl pi.dk/3/ || fetch -o - http://pi.dk/3) | bash
Для других вариантов установки см. http://git.savannah.gnu.org/cgit/parallel.git/tree/README
Подробнее
Дополнительные примеры: http://www.gnu.org/software/parallel/man.html
Смотрите видеоролики: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Пройдите через учебник: http://www.gnu.org/software/parallel/parallel_tutorial.html
Подпишитесь на список адресов электронной почты, чтобы получить поддержку: https://lists.gnu.org/mailman/listinfo/parallel
Ответ 7
Попробуйте следующее:
for i in *; do echo ${i}: `data $i`; done
Ответ 8
Поскольку вы специально спросили об этом с точки зрения "карты", я подумал, что поделюсь этой функцией, которую я имею в своей личной библиотеке оболочки:
# map_lines: evaluate a command for each line of input
map_lines()
{
while read line ; do
$1 $line
done
}
Я использую это так, как вы для решения:
$ ls | map_lines ./data
Я назвал его map_lines вместо карты, поскольку, как я предположил, в какой-то день я могу реализовать map_args, где вы будете использовать его следующим образом:
$ map_args ./data *
Эта функция будет выглядеть так:
map_args()
{
cmd="$1" ; shift
for arg ; do
$cmd "$arg"
done
}
Ответ 9
Вы можете создать оболочку script следующим образом:
#!/bin/bash
cd /path/to/your/dir
for file in `dir -d *` ; do
./data "$file"
done
Этот цикл проходит через каждый файл в /path/to/your/dir и запускает ваши "данные" script. Убедитесь, что chmod выше script, чтобы он выполнялся.
Ответ 10
Вы также можете использовать PRLL.
Ответ 11
ls
не обрабатывает пробелы, переводы строк и другие фанк файлы в именах файлов, и их следует избегать, когда это возможно.
find
полезен, если вы хотите погрузиться в поддиры или хотите использовать другие параметры (mtime, size, name).
Но многие команды обрабатывают несколько файлов сами, поэтому не нужно для цикла:
for d in * ; do du -s $d; done
но
du -s *
md5sum e*
identify *jpg
grep bash ../*.sh
Ответ 12
Я только что написал этот script, чтобы удовлетворить ту же потребность.
http://gist.github.com/kindaro/4ba601d19f09331750bd
Он использует find
для создания набора файлов для транспонирования, что позволяет более точно выбирать файлы для отображения, но также позволяет окно для более сложных ошибок.
Я разработал два режима работы: первый режим запускает команду с аргументами "исходный файл" и "целевой файл" , а второй режим исходное содержимое файла в команду как stdin и записывает его stdout в целевой файл.
Мы можем рассмотреть добавление поддержки для параллельного выполнения и, возможно, ограничить набор настраиваемых аргументов find несколькими наиболее необходимыми. Я не уверен, правильно ли это делать.