Como eu posso desreferenciar eficientemente todos os links simbólicos nos nomes de arquivos 'find' * output *?

4

Eu preciso que os caminhos sejam totalmente resolvidos e relativos a um determinado diretório. Isso deve ser feito de forma eficiente, pois o número de caminhos geralmente é maior que 100.000.

Situação : tenho diretórios que contêm principalmente links simbólicos para outros diretórios, como em

foo
 123 -> ../baz/123
 896 -> ../bar/896

(Note que foo NÃO contém apenas links simbólicos para diretórios, ele também contém arquivos comuns que eu tenho que capturar também.)

Esses diretórios com links simbólicos contêm arquivos. . Eu quero obter uma lista desses arquivos, na forma

baz/123/some.file
bar/123/other.file

Ou seja, quando "encontrar" encontrar um link simbólico, eu quero que ele aponte esse caminho quando estiver relatando o conteúdo .

Então, eu estou executando este comando a partir do diretório pai do foo:

find -L foo -type f

Mas isso não funciona.

Honestamente, você esperaria que a opção -L , que afirma "seguir links simbólicos", implementasse esse comportamento. Mas, seu comportamento real é procurar no conteúdo desses diretórios, mas reportar arquivos dentro deles com seus nomes não-referenciados, ie. os resultados parecem

foo/baz/123/some.file
foo/bar/896/another.file

Os resultados serão usados para operações de conjunto em relação a uma lista de caminhos de arquivos que são todos 1. totalmente resolvidos e 2. relativos ao diretório pai do foo , portanto todo resultado deve também cumprem esse critério. Eu posso garantir para esses propósitos que todos os links são resolvíveis, isto é. nenhum é circular ou excessivamente profundo. A maioria, mas não todos os links, apontam para diretórios em vez de arquivos.

No momento, o melhor que posso fazer é um script Python que reescreve qualquer caminho não referenciado para os resolvidos. Mas como o número de arquivos envolvidos está no intervalo 100000 + , isso não é muito prático (e ridículo, já que find já se incomodou em desreferê-los, simplesmente não retornou a referência caminhos). (EDIT: Veja o meu comentário sobre este post - Eu encontrei um não-solução (em que ele faz o trabalho de forma eficiente, mas no caminho errado - a execução de comandos externos).)

Estou convencido de que eu deveria ser capaz de executar essa tarefa apenas com find e nenhum comando externo, mas não estou achando a página de manual esclarecedora aqui - nenhum de -L , -H , -P , -follow tem o comportamento correto, nem -printf %l . -exec está fora por razões óbvias - não é interno para find . Alguma idéia?

EDIT 2: neste momento, Stephane me convenceu de que não há nenhuma razão particularmente boa para encontrar que tenha essa funcionalidade internamente, então estou disposto a aceitar qualquer resposta razoavelmente eficiente.

    
por kampu 13.06.2013 / 04:44

1 resposta

5

O que você está pedindo não faz muito sentido no caso geral, então não é surpresa que find não tenha provisão para isso.

Um symlink com um alvo relativo é relativo ao caminho do symlink. Então, por exemplo, se percorrendo um diretório seguindo os links simbólicos, find encontra a/b/c/d e a , a/b , a/b/c são todos links simbólicos relativos ou absolutos (ou links simbólicos para caminhos com componentes symlink), o que deveria faz?

Se você estiver procurando por uma diretiva find ou uma diretiva -printf % do GNU que se expande para um caminho livre de links simbólicos para o arquivo relativo ao diretório atual ou a qualquer diretório Receio que não exista nenhum.

Se você estiver no Linux, poderá obter o caminho absoluto desses arquivos com:

find -L foo -type f -exec readlink -f {} \;

Como você descobriu, existe pelo menos um comando realpath que aceita mais de um argumento de caminho que, em combinação com a sintaxe -exec cmd {} + padrão, será muito mais eficiente, pois está executando como poucos comandos do caminho real como necessário:

find -L foo -type f -exec realpath {} +

find -L foo -type f -print0 | xargs -r0 realpath

pode ser mais rápido, como se fosse necessário mais de um comando realpath , find pode continuar procurando mais arquivos enquanto o primeiro realpath começa a funcionar, o que mesmo em um sistema de processador pode torná-lo mais eficiente. / p>

-print0 e xargs -r0 não são padrão, vêm do GNU, mas são encontrados em várias outras implementações, como a maioria dos BSDs modernos.

O Zsh tem suporte embutido para ele:

print -rl foo/***/*(-.:A)

Se você não se importa com a ordem de classificação, pode desativar a classificação e torná-la um pouco mais eficiente com:

print -rl foo/***/*(-.oN:A)

Se você quiser convertê-los em caminhos relativos para o diretório atual, consulte a essa pergunta SO .

Se você sabe que todos esses arquivos têm um caminho canônico absoluto (cujo nenhum dos componentes são links simbólicos) dentro do diretório atual, você pode simplificá-lo para (ainda com zsh ):

files=(foo/***/*(-.:A))
print -rl -- ${files#$PWD/}

Apesar de ser curto e conveniente, e funciona como qualquer outro nome de arquivo, duvido que seja mais rápido que find + realpath .

Com as ferramentas Debian realpath e GNU, você pode fazer:

cd -P .
find -L foo -type f -exec realpath -z {} + | 
  gawk -v p="$PWD" -v l="${#PWD}" -v RS='
find -L foo -type f -print0 |
  xargs -r0 realpath -z --relative-base . |
  xargs -r0 whatever you want to do with them
' -vORS='
find -L foo -type f -exec readlink -f {} \;
' ' substr($0, 1, l+1) == p "/" {$0 = substr($0, l+2)}; 1' | xargs -r0 whatever you want to do with them

Como percebo agora, agora há um realpath nas versões recentes do GNU coreutils, que tem o recurso exato que você está procurando, então é só uma questão de

find -L foo -type f -exec realpath {} +

find -L foo -type f -print0 | xargs -r0 realpath

(use --relative-to . em vez de --relative-base . se você quiser caminhos relativos mesmo para arquivos cujo caminho livre do symlink não resida abaixo do diretório de trabalho atual).

    
por 13.06.2013 / 09:42