find: Por que o operador -a não é comutativo em combinação com -print?

2

Supondo que estamos usando find no diretório atual com arquivos de áudio (por exemplo, MP3) e (importante!) também arquivos não-mp3 (por exemplo, JPEG) presentes no diretório atual ou em algum lugar abaixo dele. Além disso, temos um diretório com alguns outros arquivos de áudio (chamado exclude_me no exemplo), que deve corresponder ao seu nome ao ser excluído da pesquisa de arquivos:

find . \( -type d -name 'exclude_me' -prune \) -o \( -type f -a -iname '*.mp3' -a -print \)

(Embora não seja absolutamente necessário neste caso, eu coloquei deliberadamente alguns parênteses redundantes, assim como operadores -a adicionais para fins de clareza.)

Por causa da opção explícita -print (cf. também link ), isso realmente omitirá qualquer coisa do diretório "exclude_me" e liste apenas os arquivos * .mp3 recursivamente do diretório atual. Às vezes (nem sempre) o comando alias pode já ser suficiente para um find one-liner, mas esse comando exigirá o argumento no end da linha.
Observando que o argumento '* .mp3' para -iname é antes do -print neste caso, parece não haver nenhuma maneira de resolver o problema com um alias .

Mas por que não fingir matemática simplesmente trocando as duas opções?
Assim:

find . \( -type d -name 'exclude_me' -prune \) -o \( -type f -a -print -a -iname '*.mp3' \)

Lembre-se de definir uma pré-condição para ter pelo menos um arquivo não-mp3 (por exemplo, imagem) no arquivo. diretório ou seus subdiretórios. Ele foi definido para dar uma prova mais clara de que isso não funciona como esperado sob o capô. Sem eles, as coisas simplesmente parecerão funcionando bem. Tendo eles presentes, fará com que os arquivos não-mp3 sejam listados, apesar da especificação distinta -iname '*.mp3' no one-liner. Então, por que o operador -a não funciona como na matemática, onde "A + B" é o mesmo que "B + A"? Ou, em find terms:

\( -type f -a -print -a -iname '*.mp3' \)

deve ser o mesmo que

\( -type f -a -iname '*.mp3' -a -print \)

ou não?

Não há como obter o resultado desejado e ter o argumento -iname no final da linha, e não em qualquer lugar entre os dois? (exceto para soluções hackies feias como grep -v exclude_me ou similares)

    
por syntaxerror 30.08.2014 / 20:07

3 respostas

6

and e or não são comutativos em muitas linguagens de programação. C, C ++, C♯, conchas, encontrar e muito mais.

Eles usam a esquerda para a direita avaliada, ou seja, ela avalia primeiro o termo à esquerda e só avalia o termo à direita se necessário. Portanto, em false and b , b não é avaliado, pois a resposta é sempre falsa. Mas em b and false , b é avaliado, pois ainda não foi visto que o segundo termo é falso.

Isso não faz nenhuma diferença quando os termos são funções puras, ou seja, um predicado (algo que retorna um booleano e não faz mais nada), mas quando ele faz alguma coisa (tem efeitos colaterais) então importa.

-print faz alguma coisa. Imprime, portanto, importa.

Nota: Qualquer sistema que permita efeitos colaterais (como -print), deve definir a ordem de avaliação, e da esquerda para a direita é provavelmente o mais simples.

    
por 31.08.2014 / 00:16
3

Eles não são iguais. -print primary é sempre avaliado como true e faz com que o nome do caminho atual seja gravado na saída padrão.

Quando você usa:

\( -type f -a -print -a -iname '*.mp3' \)

Todos os arquivos encontrados serão impressos no stdout, é o comportamento padrão de -print , independentemente da expressão -iname '*.mp3' ser verdadeira ou falsa.

Quando você usa:

\( -type f -a -iname '*.mp3' -a -print \)

-print parte só alcançada se -iname '*.mp3' expressão for verdadeira. Portanto, apenas os arquivos que terminam com .mp3 serão impressos.

POSIX define encontrar um operador como:

expression [-a] expression

Conjunction of primaries; the AND operator is implied by the juxtaposition of two primaries or made explicit by the optional -a operator. The second expression shall not be evaluated if the first expression is false.

Nesse caso, você não precisa usar -print , porque se a expressão for verdadeira, -print é adicionado por padrão:

If no expression is present, -print shall be used as the expression. Otherwise, if the given expression does not contain any of the primaries -exec, -ok, or -print, the given expression shall be effectively replaced by:

( given_expression ) -print

Mas isso fará com que exclude_me seja impresso na saída, porque quando find corresponde, -prune faz com que a expressão seja avaliada como verdadeira. E desde quando omitimos -print na segunda expresão, -print é adicionado ao final de ambas as expressões. Parece:

find . \( -type d -name 'exclude_me' -prune \) -print -o \( -type f -a -iname '*.mp3' \) -print

Isso faz com que exclude_me seja impresso.

Você sempre pode omitir exclude_me na saída, adicionando explicitamente um -print na segunda expressão. Isso faz com que -print seja aplicado somente para a segunda expressão:

find . \( -type d -name 'exclude_me' -prune \) -o \( -type f -a -iname '*.mp3' \) -print

Ou adicionar uma expressão depois de -prune e certifique-se de que a primeira expressão seja falsa:

find . \( -type d -name 'exclude_me' -prune -a -name . \) -o \( -type f -a -iname '*.mp3' \)
    
por 30.08.2014 / 20:30
2

Use uma função de shell em vez de um alias:

function my_find
{
   exclude_dir="$1"
   shift
   find . \( -type d -name "$exclude_dir" -prune \) -o \( -type f -a -iname "$@" -a -print \)
}
    
por 31.08.2014 / 00:23

Tags