Limitar POSIX encontrar a profundidade específica?

15

Notei recentemente que as especificações POSIX parafind não incluem o -maxdepthprimário.

Para aqueles que não estão familiarizados com isso, o objetivo do -maxdepthprimário é restringir quantos níveis de profundidade finddescerão. -maxdepth 0resulta em apenas argumentos de linha de comando sendo processados; -maxdepth 1trataria apenas os resultados diretamente nos argumentos da linha de comando, etc.

Como posso obter o comportamento equivalente ao -maxdepthprimário não POSIX usando apenas opções e ferramentas especificadas no POSIX?

(Nota: é claro que posso obter o equivalente -maxdepth 0usando apenas -pruneo primeiro operando, mas isso não se estende a outras profundidades.)

Curinga
fonte
@StevenPenny, a abordagem do FreeBSD -depth -2, -depth 1... poderia ser vista como melhor que a do GNU -maxdepth/ /-mindepth
Stéphane Chazelas
@ StéphaneChazelas de qualquer maneira - a localização do POSIX deve ter uma ou outra; então ele é aleijado
Steven Penny
1
Pelo menos para -maxdepth/ -mindepth, existem alternativas razoáveis ​​(observe que -pathé uma adição recente ao POSIX). As alternativas para -timexyou -mtime -3m(ou -mmin -3) são muito mais complicadas. Alguns gostam -execdir/ -deletenão têm alternativa confiável.
Stéphane Chazelas
2
@StevenPenny, sinta-se à vontade para registrar um ticket no site austingroupbugs.net e solicitar que ele seja adicionado. Vi coisas serem adicionadas sem a necessidade de um patrocinador quando havia uma forte justificativa. Um curso de ação provavelmente melhor seria obter o maior número de implementações primeiro, para que o POSIX tivesse que especificar o existente, que geralmente é menos controverso.
Stéphane Chazelas
@ StéphaneChazelas no meu caso, acabei nomeando os arquivos diretamente, mas obrigado; Eu poderia apresentar um bilhete, se isso vem de novo
Steven Penny

Respostas:

7

Você pode usar -pathpara corresponder a uma determinada profundidade e podar lá. Por exemplo

find . -path '*/*/*' -prune -o -type d -print

seria maxdepth 1, pois *corresponde a ., */*correspondências ./dir1e */*/*correspondências ./dir1/dir2removidas. Se você usar um diretório inicial absoluto, precisará adicionar um líder /ao -pathtambém.

meuh
fonte
Hummm, complicado. Você não pode simplesmente remover uma camada do /*final do padrão, remover o -ooperador e obter o mesmo resultado?
Curinga
Não, porque *combina /também, então o dir a/b/c/d/ecaberia -path */*, infelizmente.
Meu # 11/16
Mas a/b/c/d/enunca seria alcançado , pois -pruneseria aplicada a a/b....
Wildcard
1
Desculpe, eu li isso errado -prunee fui -oremovido. Se você mantiver o -pruneproblema, isso */*não corresponderá a nada em um nível acima da profundidade máxima, por exemplo, o diretório único a.
Meuhttp
11

A abordagem de @ meuh é ineficiente, pois -maxdepth 1ainda permite findler o conteúdo dos diretórios no nível 1 para depois ignorá-los. Também não funcionará corretamente com algumas findimplementações (incluindo GNU find) se alguns nomes de diretório contiverem seqüências de bytes que não formam caracteres válidos no código do idioma do usuário (como para nomes de arquivos em uma codificação de caracteres diferente).

find . \( -name . -o -prune \) -extra-conditions-and-actions

é a maneira mais canônica de implementar o GNU -maxdepth 1(ou FreeBSD -depth -2).

Geralmente, porém, é o que -depth 1você deseja ( -mindepth 1 -maxdepth 1), como não deseja considerar .(profundidade 0), e é ainda mais simples:

find . ! -name . -prune -extra-conditions-and-actions

Pois -maxdepth 2, isso se torna:

find . \( ! -path './*/*' -o -prune \) -extra-conditions-and-actions

E é aí que você executa os problemas de caracteres inválidos.

Por exemplo, se você tiver um diretório chamado, Stéphanemas écodificado no charset iso8859-1 (também conhecido como latin1) (0xe9 byte), como era mais comum na Europa Ocidental e na América até meados da década de 2000, esse byte 0xe9 não é um caractere válido em UTF-8. Portanto, nos códigos de idioma UTF-8, o *curinga (com algumas findimplementações) não corresponderá Stéphanecom *0 ou mais caracteres e 0xe9 não é um caractere.

$ locale charmap
UTF-8
$ find . -maxdepth 2
.
./St?phane
./St?phane/Chazelas
./Stéphane
./Stéphane/Chazelas
./John
./John/Smith
$ find . \( ! -path './*/*' -o -prune \)
.
./St?phane
./St?phane/Chazelas
./St?phane/Chazelas/age
./St?phane/Chazelas/gender
./St?phane/Chazelas/address
./Stéphane
./Stéphane/Chazelas
./John
./John/Smith

Meu find(quando a saída vai para um terminal) exibe esse byte 0xe9 inválido como ?acima. Você pode ver que St<0xe9>phane/Chazelasnão era pruned.

Você pode contornar isso fazendo:

LC_ALL=C find . \( ! -path './*/*' -o -prune \) -extra-conditions-and-actions

Mas observe que isso afeta todas as configurações de localidade finde qualquer aplicativo executado (como por meio dos -execpredicados).

$ LC_ALL=C find . \( ! -path './*/*' -o -prune \)
.
./St?phane
./St?phane/Chazelas
./St??phane
./St??phane/Chazelas
./John
./John/Smith

Agora, eu realmente entendo -maxdepth 2como o é no segundo Stéphane corretamente codificado em UTF-8 é exibido ??como os bytes 0xc3 0xa9 (considerados como dois caracteres indefinidos individuais no código de idioma C) da codificação UTF-8 de é caracteres imprimíveis no código de idioma C.

E se eu tivesse adicionado um -name '????????', eu teria pegado o Stéphane errado (aquele codificado em iso8859-1).

Para aplicar a caminhos arbitrários em vez de ., faça:

find some/dir/. ! -name . -prune ...

para -mindepth 1 -maxdepth 1ou:

find some/dir/. \( ! -path '*/./*/*' -o -prune \) ...

para -maxdepth 2.

Eu ainda faria um:

(cd -P -- "$dir" && find . ...)

Primeiro, porque isso torna os caminhos mais curtos, o que torna menos provável que ocorram em problemas de caminho muito longo ou a lista de argumentos muito longa , mas também para contornar o fato de que findnão é possível suportar argumentos arbitrários de caminho (exceto -fcom o FreeBSD find), pois ele se engasga com valores de $dirlike !ou -print...


A -ocombinação com negação é um truque comum para executar dois conjuntos independentes de -condition/ -actionin find.

Se você deseja executar -action1em reunião de arquivos -condition1e independentemente -action2em reunião de arquivos -condition2, não é possível:

find . -condition1 -action1 -condition2 -action2

Como -action2seria executado apenas para arquivos que atendam às duas condições.

Nem:

find . -contition1 -action1 -o -condition2 -action2

Como -action2não seria executado para arquivos que atendam às duas condições.

find . \( ! -condition1 -o -action1 \) -condition2 -action2

funciona como se \( ! -condition1 -o -action1 \)fosse verdadeiro para todos os arquivos. Isso pressupõe que -action1é uma ação (como -prune, -exec ... {} +) que sempre retorna verdadeira . Para ações como -exec ... \;essa, pode retornar falso , você pode adicionar outro -o -somethingonde -somethingé inofensivo, mas retorna verdadeiro como -trueno GNU findou -links +0ou -name '*'(embora observe o problema sobre caracteres inválidos acima).

Stéphane Chazelas
fonte
1
Algum dia, encontrarei um monte de arquivos chineses e ficarei muito feliz em ter lido suas muitas respostas sobre localidade e caracteres válidos. :)
Curinga
2
@Wildcard, é mais provável que você (e mais ainda um chinês) tenha problemas com nomes de arquivos britânicos, franceses ... do que nomes de arquivos chineses, pois os nomes de arquivos chineses são codificados em UTF-8 com mais freqüência do que os nomes de arquivos de scripts alfabéticos isso geralmente pode ser coberto por um conjunto de caracteres de byte único, que era a norma até relativamente recentemente. Existem outros conjuntos de caracteres de vários bytes para cobrir o caractere chinês, mas eu esperaria que o povo chinês tivesse mudado para UTF-8 mais cedo do que os ocidentais, pois esses conjuntos de caracteres têm uma série de problemas desagradáveis. Veja também a edição para um exemplo.
Stéphane Chazelas
0

Encontrei um problema em que precisava de uma maneira de limitar a profundidade ao pesquisar vários caminhos (em vez de apenas .).

Por exemplo:

$ find dir1 dir2 -name myfile -maxdepth 1

Isso me levou a uma abordagem alternativa usando -regex. A essência é:

-regex '(<list of paths | delimited>)/<filename>'

Portanto, o acima seria:

$ find dir1 dir2 -name myfile -regextype awk -regex '(dir1|dir2)/myfile' # GNU
$ find -E dir1 dir2 -name myfile -regex '(dir1|dir2)/myfile' # MacOS BSD

Sem um nome de arquivo:

$ find dir1 dir2 -name myfile -maxdepth 1 # GNU

-regex '(<list of paths | delimited>)/<anything that's not a slash>$'

$ find dir1 dir2 -name myfile -regextype awk -regex '(dir1|dir2)/[^/]*$' # GNU
$ find -E dir1 dir2 -name myfile -regex '(dir1|dir2)/[^/]*$' # MacOS BSD

Por fim, para -maxdepth 2o regex muda para:'(dir1|dir2)/([^/]*/){0,1}[^/]*$'

Alissa H
fonte
1
Esta pergunta pede uma solução padrão (como no POSIX). Também -maxdepthfuncionaria com vários caminhos de pesquisa.
Kusalananda