encontre -exec com vários comandos

430

Eu estou tentando usar find -exec com vários comandos sem nenhum sucesso. Alguém sabe se comandos como os seguintes são possíveis?

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

Basicamente, estou tentando imprimir a última linha de cada arquivo txt no diretório atual e imprimir no final da linha, uma vírgula seguida pelo nome do arquivo.

Andy
fonte
4
superuser.com/questions/236601/…
Ignacio Vazquez-Abrams
1
Quanto a verificar a possibilidade do comando, você não o testou em seu sistema?
Sriram
1
A partir da findpágina do manual: 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
Veja também: Unix.stackexchange.com/questions/156008/…
Kusalananda
O link do @ JVE999 está quebrado, alternativa em ss64.com/bash/find.html #
Keith M

Respostas:

659

findaceita várias -execpartes para o comando. Por exemplo:

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

Observe que, neste caso, o segundo comando será executado apenas se o primeiro retornar com êxito, conforme mencionado por @Caleb. Se você deseja que ambos os comandos sejam executados independentemente do sucesso ou falha, você pode usar esta construção:

find . -name "*.txt" \( -exec echo {} \; -o -exec true \; \) -exec grep banana {} \;
Funileiro
fonte
como grep duas vezes? isto está falhando: encontre ./* -exec grep -v 'COLD,' {} \; -exec egrep -i "minha_string" {} \;
rajeev 22/01
51
@rajeev O segundo exec só será executado se o código de retorno do primeiro retornar com êxito, caso contrário ele será ignorado. Provavelmente isso deve ser observado nesta resposta.
Caleb em
1
Esta seria a resposta #
pylover
1
Observe o uso de -nalgumas das outras respostas para suprimir a nova linha gerada pelo eco, o que é útil se o seu segundo comando produzir apenas uma linha de saída e você desejar que sejam mais fáceis de ler.
precisa
Ótima solução. Funciona como um encanto.
Philippe Delteil 6/03/19
98
find . -type d -exec sh -c "echo -n {}; echo -n ' x '; echo {}" \;
Avari
fonte
3
Se você deseja executar Bash em vez de Bourne você também pode usar ... -exec bash -c ...em vez de ... -exec sh -c ....
Kenny Evitt 15/10
9
Nunca incorpore {}no código do shell. Veja unix.stackexchange.com/questions/156008/…
Kusalananda 3/17/17
3
+1 @Kusalananda A injeção de nomes de arquivos é frágil e insegura. Use parâmetros. veja SC2156
pambda
52

Um dos seguintes:

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"' _ {} \;
Pausado até novo aviso.
fonte
12
Para que serve o sublinhado antes de {}?
qed
2
@ qed: é um valor de descarte que ocupa o lugar de $0. Tente isso com "foobar" em vez de "_": find /usr/bin -name find -exec sh -c 'echo "[$0] [$1]"' foobar {} \;- a saída: "[foobar] [/ usr / bin / find]".
Pausado até novo aviso.
1
@XuWang: Sim, eu diria que é esse o caso. Como você sabe, $0geralmente é o nome do programa ( ARGV[0]).
Pausado até novo aviso.
3
É fundamental, para esse método, que o script passado sh -cseja entre aspas simples, não duplas. Caso contrário, $1está no escopo errado.
Nick
1
As aspas @ Nick não têm nada a ver com isso - você pode escrever '$1'com aspas duplas desde que escape da variável ( "\$1"). Você também pode escapar de outros caracteres ( "\"").
Camilo Martin
22

Outra maneira é assim:

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

em uma linha

multiple_cmd() { tail -1 $1; ls $1 }; export -f multiple_cmd; find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;
  • " multiple_cmd()" - é uma função
  • " export -f multiple_cmd" - exportará para que qualquer outro subshell possa vê-lo
  • " find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;" - encontre que executará a função no seu exemplo

Dessa maneira, multiple_cmd pode ser tão longo e complexo quanto você precisar.

Espero que isto ajude.

al3x2ndru
fonte
perfeito, exatamente o que eu precisava!
Anentropic
não funciona para mim no OSX
Thomas
@ Thomas faz isso, mas tente este forro, testado em osx. Eu criei um diretório chamado 'aaa' com alguns arquivos / diretórios e CDd nele. Então, ~/aaa$ acmd() { echo x \"$1\" x; }; export -f acmd; find . -exec bash -c 'acmd {}' \;
barlop
@ Barlop, vou tentar; obrigado!
Thomas
14

Existe uma maneira mais fácil:

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

Alternativamente:

while read -r file; do
    echo "look at my $file, my $file is amazing";
done <<< "$(find ...)"
Camilo Martin
fonte
1
nomes de arquivos pode ter quebras de linha neles, é por isso achado tem o argumento -print0 e xargs tem a -0 argumento
abasterfield
@abasterfield Eu sempre espero nunca encontrar aqueles no lol selvagem
Camilo Martin
1
o que eu queria fazer era "encontrar ... -exec zcat {} | wc -l \;" o que não funcionou. No entanto, encontre ... | enquanto lê -r arquivo; echo "$ file: zcat $file | wc -l"; done funciona, então obrigado!
Greg Dougherty
No comentário acima, eu tenho "back ticks" em torno de "zcat $ file | wc -l". Infelizmente, o SO os transforma em formatação, então eu o adicionei como uma resposta real, com o código correto visível
Greg Dougherty
1
@GregDougherty Você pode escapar dos backticks `para fazer isso usando barras invertidas da seguinte forma: \​`(ainda, esse é outro bom motivo para usar $()).
Camilo Martin
8

Não sei se você pode fazer isso com o find, mas uma solução alternativa seria criar um script de shell e executá-lo com o find.

lastline.sh:

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

Tornar o script executável

chmod +x lastline.sh

Use find:

find . -name "*.txt" -exec ./lastline.sh {} \;
Andrea Spadaccini
fonte
7
Como os backticks foram descontinuados, incentive o uso de $ (...) que seja melhor legível, independentemente da fonte e fácil de aninhar. Obrigado.
desconhecido utilizador
4

A primeira resposta de Denis é a resposta para resolver o problema. Mas, na verdade, não é mais uma descoberta com vários comandos em apenas um executivo como o título sugere. Para responder a um executivo com vários comandos, teremos que procurar outra coisa para resolver. Aqui está um exemplo:

Mantenha as últimas 10000 linhas de arquivos .log que foram modificados nos últimos 7 dias usando 1 comando exec usando várias referências {}

1) veja o que o comando fará em quais arquivos:

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

2) Faça: (não observe "\>", mas apenas ">" isso é desejado)

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

Eric Duruisseau
fonte
Mas isso será interrompido se um dos nomes de arquivos tiver um espaço, eu acredito.
Camilo Martin
4

Graças a Camilo Martin, pude responder a uma pergunta relacionada:

O que eu queria fazer era

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

o que não funcionou. Contudo,

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

funciona, então obrigado!

Greg Dougherty
fonte
2

Estendendo a resposta do @ Tinker,

No meu caso, eu precisava fazer um command | command | commandinterior no-exec para imprimir o nome do arquivo e o texto encontrado nos arquivos que contêm um determinado texto.

Consegui fazer isso com:

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

o resultado é:

    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
fonte
1
Você também pode imprimir o nome do arquivo e o conteúdo grep'd em uma única linha, passando /dev/nullcomo um segundo argumento para o grepcomando com um -execparâmetro:find . -name config -type f -exec grep "bitbucket" {} /dev/null \;
Bill Feth
1

deve usar xargs :)

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

outro (trabalhando no osx)

find *.txt -type f -exec echo ,$(PWD) {} + -exec tail -1 {} + | tr ' ' '/'
smapira
fonte
3
Isso ignora um caso de uso importante para findsituações em que o número de arquivos correspondentes é muito grande para uma linha de comando. -execé uma maneira de contornar esse limite. A tubulação para um utilitário perde esse benefício.
Chris Johnson
xargs -nexiste para escolher o número de correspondências por chamada. xargs -n 1 foocmdserá executado foocmd {}para cada partida.
AndrewF
0

Uma find+xargsresposta

O exemplo abaixo encontra todos os .htmlarquivos e cria uma cópia com a .BAKextensão anexada (por exemplo, 1.html> 1.html.BAK).

Comando único com vários espaços reservados

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

Vários comandos com vários espaços reservados

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

Este comando também funcionará com arquivos que começam com um hífen ou contêm espaços, como -my file.htmlgraças à citação de parâmetros e --depois o cpsinal para cpo final dos parâmetros e o início dos nomes de arquivos reais.

-print0 canaliza os resultados com terminadores de bytes nulos.


para xargs, o -I {}parâmetro define {}como o espaço reservado; você pode usar o espaço reservado que desejar; -0indica que os itens de entrada são separados por nulo.

ccpizza
fonte