Эквивалент bash "map": запустить команду для каждого файла - PullRequest
13 голосов
/ 14 апреля 2010

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

Например, скажем, у меня есть программа data, которая выводит важный файл о файле:

./data foo
137
./data bar
42

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

map data `ls *`
ls * | map data

, чтобы получить вывод, как это:

foo: 137
bar: 42

Ответы [ 12 ]

15 голосов
/ 17 апреля 2010

Если вы просто пытаетесь выполнить вашу программу data для группы файлов, самый простой / наименее сложный способ - использовать -exec в find.

Скажем, вы хотите выполнить data для всех текстовых файлов в текущем каталоге (и подкаталогах). Это все, что вам нужно:

find . -name "*.txt" -exec data {} \;

Если вы хотите ограничить его текущим каталогом, вы можете сделать это:

find . -maxdepth 1 -name "*.txt" -exec data {} \;

Есть множество опций с find.

8 голосов
/ 14 апреля 2010

Если вы просто хотите запустить команду для каждого файла, вы можете сделать это:

for i in *; do data "$i"; done

Если вы также хотите отобразить имя файла, над которым он сейчас работает, вы можете использовать это:

for i in *; do echo -n "$i: "; data "$i"; done
7 голосов
/ 14 апреля 2010

Похоже, вы хотите xargs:

find . --maxdepth 1 | xargs -d'\n' data

Чтобы напечатать каждую команду сначала, она становится немного более сложной:

find . --maxdepth 1 | xargs -d'\n' -I {} bash -c "echo {}; data {}"
5 голосов
/ 15 апреля 2010

Вам следует избегать разбора 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.

3 голосов
/ 29 апреля 2015

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

3 голосов
/ 14 апреля 2010

Обычные методы:

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 )

Вы можете легко обернуть первый в скрипте:

#!/bin/bash
# map.bash

while read file; do
    "$1" "$file"
done

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

Конечно, вы также можете просто использовать утилиту xargs:

find -maxdepth 0 * | xargs -n 1 data

Обратите внимание, что вы должны убедиться, что индикаторы выключены (ls --indicator-style=none), если вы обычно их используете, или @, добавленный к символическим ссылкам, превратит их в несуществующие имена файлов.

2 голосов
/ 15 апреля 2010

Поскольку вы специально спрашивали об этом в терминах «карты», я решил поделиться этой функцией, которая есть в моей личной библиотеке оболочек:

# 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, как я предполагал, что когда-нибудь я смогу реализовать map_args, где вы будете использовать его следующим образом:

$ map_args ./data *

Эта функция будет выглядетькак это:

map_args()
{
    cmd="$1" ; shift
    for arg ; do
        $cmd "$arg"
    done
}
1 голос
/ 14 апреля 2010

Попробуйте это:

for i in *; do echo ${i}: `data $i`; done
0 голосов
/ 17 августа 2015

Я только что написал этот скрипт специально для удовлетворения той же потребности.

http://gist.github.com/kindaro/4ba601d19f09331750bd

Он использует find для создания набора файлов для транспонирования, что позволяет более точно выбирать файлы для сопоставления, но также позволяет использовать окно для более серьезных ошибок.

Я спроектировал два режима работы : первый режим запускает команду с аргументами "исходный файл" и "целевой файл" , тогда как второй режим предоставляет исходный файл содержимое команды в виде stdin и записывает ее стандартный вывод в целевой файл.

Мы можем дополнительно рассмотреть добавив поддержку параллельного выполнения и, возможно, , ограничив набор пользовательских аргументов поиска несколькими наиболее необходимыми. Я не совсем уверен, правильно ли это делать.

0 голосов
/ 15 апреля 2010

ls не обрабатывает пробелы, переводы строк и другие прикольные вещи в именах файлов и по возможности их следует избегать.

find полезно только в том случае, если вы хотите погрузиться в подкаталоги или если вы хотите использовать другие параметры (mtime, size, назовите его).

Но многие команды сами обрабатывают несколько файлов, поэтому цикл for не требуется:

for d in * ; do du -s $d; done

но

du -s *
md5sum e* 
identify *jpg
grep bash ../*.sh
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...