Comando Complex find com várias podas

1

Eu preciso percorrer uma árvore de diretórios com vários pontos de partida, por exemplo find ./User1 ./User2 ./User3 mas processa apenas nomes de diretórios específicos que existem em qualquer um desses pontos iniciais, como este: ./User1/Documents ./User1/Pictures /.User2/Documents etc. Eu gostaria de pular e prune em qualquer outro subpastas não em uma determinada lista de todos os pontos de partida.

Todas as minhas tentativas de várias combinações de -not e prune com \( & \) falhou.

Os pontos de partida serão determinados em um script no tempo de execução. Como último recurso, eu poderia criar uma lista de pontos iniciais fazendo um loop pela lista de User? e gerando uma lista ainda maior de pontos iniciais com os nomes de diretório desejados já anexados.

Apenas ./User?/NotInList deve ser excluído, mas ./User?/InList/NotInList está OK.

[A grande figura: Estou fazendo a recuperação de dados e quero enumerar arquivos "importantes" na pasta Users do Microsoft Windows. Eu preciso pular as pastas Public e Default (então eu construo uma seqüência de pastas remanescentes como pontos de partida para o comando find ), e também só seleciono / desço no Pictures Documents etc pastas de cada usuário, mas não as pastas AppData ou Contacts etc. que raramente são importantes.]

    
por afuna 12.08.2018 / 21:24

3 respostas

3

com zsh :

users=(User1 User2)
# or users=($(<userlist.txt))
# if userlist.txt contains a list of users as separate words

dirs=(Documents Pictures ...)

find ./$^users/$^dirs

Mesmo com fish :

set users User1 User2 ...
set dirs Documents Pictures
find ./$users/$dirs

Em fish ou zsh -o rcexpandparam , as matrizes se expandem de maneira expansiva. Em zsh , a sintaxe $^array ativa rcexpandparam para essa expansão de matriz única.

A referência a rc é um pouco enganadora aqui. Enquanto $array^string em rc / es (onde array é (1 2)) expandiria como {1,2}string (e essa é a razão pela qual zsh escolheu ^ para seu tipo de expansão rc em $^array ), isso não se aplica a unir matrizes. Em rc , $array1^$arrat2 (igual a $array1$array2 ) só funciona para matrizes do mesmo tamanho e une o elemento um por um ( (1 2)^(a b) se torna 1a 2b , não 1a 1b 2a 2b ).

Observe que esses não são globbing , User1/Documents será passado para find , independentemente de o arquivo existir ou não. Para passar a lista desses arquivos ou diretórios que realmente existem , em zsh , você poderia fazer

find ./$^users/$^dirs(N)

que passa adiciona o qualificador (N) glob a todo o elemento resultante da multiplicação da matriz, que tem dois efeitos:

  • torna-os globs, o que significa que eles serão expandidos para arquivos correspondentes
  • se o glob não corresponder a nenhum arquivo (eles podem corresponder apenas a 0 ou 1, pois não há nenhum caractere curinga), o glob resultante se expande para nada.

Alternativamente, você pode ir glob até o fim:

find (User1|User2)/(Documents|Pictures)

Ou com base no seu exemplo:

set -o extendedglob # best in ~/.zshrc
find ./^(#i)(Default|Public)/(Documents|Pictures)

Ou gere esse glob com base nos arrays com

find (${(j:|:)~${(b)users}})/${(j:|:)~${(b)dirs}}

Onde:

  • (b) cita os operadores de caractere curinga, se houver algum nos elementos da matriz
  • (j:|:) une-se a eles com |
  • ~ ativa a globulação na expansão

Eles trabalham com nomes de arquivos arbitrários (exceto aqueles que começam com - como uma limitação de find ).

Fazer isso com find sozinho seria muito complicado, especialmente se você quiser permitir nomes de arquivos arbitrários. Mas para seus relativamente domados como os Default , Public ...), você poderia tentar:

LC_ALL=C find . -name . -o -path './*/*' \( ! -path './*/*/*' \
  ! -name Documents ! -name Pictures -prune -o -print \) -o   \
  ! -name Default ! -name Public -o -prune

(Esse tipo de coisa me dá dor de cabeça).

Usando a sintaxe find padrão, se você quiser uma correspondência sem distinção entre maiúsculas e minúsculas, use -name '[pP][uU][bB][lL][iI][cC]' ou, se estiver usando o GNU find (que pareça) ou compatível, use -iname Public .

    
por 12.08.2018 / 22:06
2

A julgar pela resposta que você postou, você deseja escolher os diretórios ./x/y , em que x é qualquer um dos diretórios no diretório atual, exceto Public ou Default e y é qualquer de Documents , Pictures ou My Documents .

No Bash, você pode fazer isso com um glob quando extglob estiver habilitado. (Em ksh, é o padrão quando o padrão está diretamente na linha de comando; em Zsh, você precisa usar setopt kshglob .)

Dado:

$ mkdir -p {Someuser,"other user",Public,Default}/{Pictures,"My Documents",Uselessdir}

o glob aqui deve fazer o que você quer:

$ shopt -s extglob
$ printf "%s\n" ./!(Public|Default)/@(Documents|My Documents|Pictures)
./other user/My Documents
./other user/Pictures
./Someuser/My Documents
./Someuser/Pictures

Então, você deve ser capaz de simplesmente executar

shopt -s extglob
find ./!(Public|Default)/@(Documents|My Documents|Pictures) ...

O glob não será expandido para diretórios que não existem, portanto, você não receberá erros de find se houver ./Someuser/Pictures .

Adicione shopt -s nocaseglob se você quiser uma correspondência sem distinção entre maiúsculas e minúsculas.

    
por 13.08.2018 / 11:31
0

Com base no comentário de @StephaneChazelas usando a expansão de chaves, aqui está a solução que escolhi para bash que trabalha com espaços em nomes de arquivos e lida com casos de borda como apenas um usuário.

A string para expansão de chaves será armazenada em uma variável, então precisamos usar eval para expandi-la.

# escape spaces with backslash
directories=Documents,Pictures,My\ Documents

# get usernames to use with expansion
# and escape any spaces using sed
user_folders=$(find . -maxdepth 1 -mindepth 1 -not -iname Public -a -not -iname Default -type d -printf "%f," | \
sed 's| |\ |g' )   

# remove trailing comma from last user's name
user_folders=${user_folders::-1}    

# only use brace expansion if there is more than one user; if there's only
# one value it won't expand and we'll be left with braces
[[ $user_folders = *,* ]] && user_folders="{$user_folders}"

find_string="find $user_folders/{$directories}"
eval "$find_string"
    
por 12.08.2018 / 22:47