найти -exec с несколькими командами

431

Я пытаюсь использовать find -exec с несколькими командами без какого-либо успеха. Кто-нибудь знает, возможны ли такие команды, как следующие?

find *.txt -exec echo "$(tail -1 '{}'),$(ls '{}')" \;

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

Энди
источник
4
superuser.com/questions/236601/…
Игнасио Васкес-Абрамс
1
Что касается проверки возможности команды, вы не пробовали ее в своей системе?
Шрирам
1
Со findстраницы руководства: There are unavoidable security problems surrounding use of the -exec option; you should use the -execdir option instead.unixhelp.ed.ac.uk/CGI/man-cgi?find
JVE999
1
Связано: unix.stackexchange.com/questions/156008/…
Кусалананда
@ JVE999 ссылка не работает, альтернатива на ss64.com/bash/find.html
Кит М

Ответы:

660

findпринимает несколько -execчастей к команде. Например:

find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;

Обратите внимание, что в этом случае вторая команда будет выполняться только в том случае, если первая команда вернется успешно, как упомянуто @Caleb. Если вы хотите, чтобы обе команды выполнялись независимо от их успеха или неудачи, вы можете использовать эту конструкцию:

find . -name "*.txt" \( -exec echo {} \; -o -exec true \; \) -exec grep banana {} \;
паять
источник
как grep дважды? это терпит неудачу: find ./* -exec grep -v 'COLD,' {} \; -exec egrep -i "my_string" {} \;
Раджив
51
@rajeev Второй exec будет запускаться только в том случае, если код возврата для первого возвращает успех, иначе он будет пропущен. Это, вероятно, следует отметить в этом ответе.
Калеб
1
Это будет ответ
пиеловер
1
Обратите внимание на использование -nв некоторых других ответах для подавления новой строки, сгенерированной echo, что удобно, если ваша вторая команда выдает только одну строку вывода, и вы хотите, чтобы их было легче читать.
Уильям Туррелл,
Отличное решение. Работает как шарм.
Филипп Делтейл
98
find . -type d -exec sh -c "echo -n {}; echo -n ' x '; echo {}" \;
Avari
источник
3
Если вы хотите запустить Bash вместо Bourne, вы также можете использовать ... -exec bash -c ...вместо ... -exec sh -c ....
Кенни Эвитт
9
Никогда не вставляйте {}в код оболочки. См. Unix.stackexchange.com/questions/156008/…
Кусалананда
3
+1 @Kusalananda Внедрение имен файлов хрупко и небезопасно. Используйте параметры. см. SC2156
Памбда
52

Одно из следующего:

find *.txt -exec awk 'END {print $0 "," FILENAME}' {} \;

find *.txt -exec sh -c 'echo "$(tail -n 1 "$1"),$1"' _ {} \;

find *.txt -exec sh -c 'echo "$(sed -n "\$p" "$1"),$1"' _ {} \;
Приостановлено до дальнейшего уведомления.
источник
12
Зачем нужно подчеркивание перед {}?
ч.т.д.
2
@qed: это однозначное значение, которое занимает место $0. Попробуйте это с "foobar" вместо "_": find /usr/bin -name find -exec sh -c 'echo "[$0] [$1]"' foobar {} \;- вывод: "[foobar] [/ usr / bin / find]".
Приостановлено до дальнейшего уведомления.
1
@XuWang: Да, я бы сказал, что это так. Как известно, $0обычно это имя программы ( ARGV[0]).
Приостановлено до дальнейшего уведомления.
3
Для этого метода очень важно, чтобы переданный скрипт был sh -cв одинарных кавычках, а не в двойных. В противном случае $1это не в том объеме.
Ник
1
Кавычки @Nick не имеют к этому никакого отношения - вы можете писать '$1'двойные кавычки, пока вы не используете переменную ( "\$1"). Вы также можете избежать других персонажей ( "\"").
Камило Мартин
22

Другой способ таков:

multiple_cmd() { 
    tail -n1 $1; 
    ls $1 
}; 
export -f multiple_cmd; 
find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;

в одну строку

multiple_cmd() { tail -1 $1; ls $1 }; export -f multiple_cmd; find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;
  • « multiple_cmd()» - это функция
  • " export -f multiple_cmd" - экспортирует его, чтобы любой другой подоболочек мог его увидеть
  • « find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;» - найдите, что выполнит функцию на вашем примере

Таким образом, multip_cmd может быть настолько длинным и сложным, насколько вам нужно.

Надеюсь это поможет.

al3x2ndru
источник
идеально, именно то, что мне нужно!
Anentropic
у меня не работает на OSX
Томас
@Tommas, но попробуй этот лайнер, протестированный в OSX. Я сделал каталог с именем «aaa», в котором есть несколько файлов / каталогов и CDD. Тогда, ~/aaa$ acmd() { echo x \"$1\" x; }; export -f acmd; find . -exec bash -c 'acmd {}' \;
Барлоп
@ barlop, я попробую; Спасибо!
Томас
14

Есть более простой способ:

find ... | while read -r file; do
    echo "look at my $file, my $file is amazing";
done

В качестве альтернативы:

while read -r file; do
    echo "look at my $file, my $file is amazing";
done <<< "$(find ...)"
Камило Мартин
источник
1
в именах файлов могут быть символы новой строки, поэтому find имеет аргумент -print0, а xargs - -0
abasterfield
@abasterfield Я всегда надеюсь никогда не найти тех в дикой природе LOL
Камило Мартин
1
то, что я хотел сделать, это "найти ... -exec zcat {} | wc -l \;" который не работал. Тем не менее, найти ... | пока читаешь -r файл; do echo "$ file: zcat $file | wc -l"; сделано работает, так что спасибо!
Грег Догерти
В комментарии выше у меня есть "обратные галочки" вокруг "zcat $ file | wc -l". К сожалению, ТАК превращает их в форматирование, поэтому я добавил их как фактический ответ с видимым правильным кодом
Грег Догерти,
1
@GregDougherty Вы можете избежать обратных косяков `, чтобы использовать обратную косую черту следующим образом: \​`(тем не менее, это еще одна веская причина для использования $()вместо).
Камило Мартин
8

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

lastline.sh:

echo $(tail -1 $1),$1

Сделайте скрипт исполняемым

chmod +x lastline.sh

Используйте find:

find . -name "*.txt" -exec ./lastline.sh {} \;
Андреа Спадаччини
источник
7
обратные пометки не рекомендуются, пожалуйста, поощряйте использование $ (...), который лучше читается, зависит от шрифта и легко вкладывается. Спасибо.
пользователь неизвестен
4

1-й ответ Дениса - это решение проблемы. Но на самом деле это уже не находка с несколькими командами только в одном exec, как предполагает заголовок. Чтобы ответить на один exec несколькими командами, нам нужно искать что-то еще для разрешения. Вот пример:

Сохраните последние 10000 строк .log файлов, которые были изменены за последние 7 дней, используя 1 команду exec, используя несколько ссылок {}

1) посмотреть, что команда будет делать с какими файлами:

find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "echo tail -10000 {} \> fictmp; echo cat fictmp \> {} " \;

2) Сделайте это: (обратите внимание, не больше "\>", а только ">", это нужно)

find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "tail -10000 {} > fictmp; cat fictmp > {} ; rm fictmp" \;

Эрик Дюруйсо
источник
Но это сломается, если у одного из имен файлов будет пробел, я считаю.
Камило Мартин
4

Благодаря Камило Мартину я смог ответить на связанный вопрос:

То, что я хотел сделать, было

find ... -exec zcat {} | wc -l \;

который не работал. Однако,

find ... | while read -r file; do echo "$file: `zcat $file | wc -l`"; done

работает, так что спасибо!

Грег Догерти
источник
2

Расширяя ответ @ Тинкер,

В моем случае мне нужно было сделать command | command | commandвнутри, -execчтобы напечатать и имя файла, и найденный текст в файлах, содержащих определенный текст.

Я смог сделать это с:

find . -name config -type f \( -exec  grep "bitbucket" {} \; -a -exec echo {} \;  \) 

результат:

    url = git@bitbucket.org:a/a.git
./a/.git/config
    url = git@bitbucket.org:b/b.git
./b/.git/config
    url = git@bitbucket.org:c/c.git
./c/.git/config
user9869932
источник
1
Вы также можете напечатать имя файла и содержимое grep'd в одной строке, передав /dev/nullв качестве второго аргумента grepкоманду с одним -execпараметром:find . -name config -type f -exec grep "bitbucket" {} /dev/null \;
Билл Фет
1

следует использовать xargs :)

find *.txt -type f -exec tail -1 {} \; | xargs -ICONSTANT echo $(pwd),CONSTANT

еще один (работает на OSX)

find *.txt -type f -exec echo ,$(PWD) {} + -exec tail -1 {} + | tr ' ' '/'
smapira
источник
3
Это упускает основной вариант использования для findситуаций, когда количество подходящих файлов слишком велико для командной строки. -execэто способ обойти этот предел. Выпуск к коммунальной службе пропускает ту выгоду.
Крис Джонсон
xargs -nсуществует для выбора количества совпадений на один вызов. xargs -n 1 foocmdбудет выполнять foocmd {}для каждого матча.
AndrewF
0

find+xargsОтвет.

Пример ниже находит все .htmlфайлы и создает копию с .BAKдобавленным расширением (например, 1.html> 1.html.BAK).

Одна команда с несколькими заполнителями

find . -iname "*.html" -print0 | xargs -0 -I {} cp -- "{}" "{}.BAK"

Несколько команд с несколькими заполнителями

find . -iname "*.html" -print0 | xargs -0 -I {} echo "cp -- {} {}.BAK ; echo {} >> /tmp/log.txt" | sh

# if you need to do anything bash-specific then pipe to bash instead of sh

Эта команда также будет работать с файлами, которые начинаются с дефиса или содержат пробелы, например, в -my file.htmlкавычках параметров и --после, cpкоторые указывают cpна конец параметров и начало фактических имен файлов.

-print0 передает результаты с нулевыми байтами.


для xargs-I {} параметр определяет {}как заполнитель; вы можете использовать любой заполнитель, который вам нравится; -0указывает, что входные элементы разделены нулями.

ccpizza
источник