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 несколькими наиболее необходимыми. Я не уверен, правильно ли это делать.